New enumeration system
This commit is contained in:
4884
data/timthumbs.txt
4884
data/timthumbs.txt
File diff suppressed because it is too large
Load Diff
8
lib/common/collections/vulnerabilities.rb
Normal file
8
lib/common/collections/vulnerabilities.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/vulnerabilities/output'
|
||||
|
||||
class Vulnerabilities < Array
|
||||
include Vulnerabilities::Output
|
||||
|
||||
end
|
||||
13
lib/common/collections/vulnerabilities/output.rb
Normal file
13
lib/common/collections/vulnerabilities/output.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class Vulnerabilities < Array
|
||||
module Output
|
||||
|
||||
def output
|
||||
self.each do |v|
|
||||
v.output
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
10
lib/common/collections/wp_items.rb
Executable file
10
lib/common/collections/wp_items.rb
Executable file
@@ -0,0 +1,10 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_items/detectable'
|
||||
require 'common/collections/wp_items/output'
|
||||
|
||||
class WpItems < Array
|
||||
extend WpItems::Detectable
|
||||
include WpItems::Output
|
||||
|
||||
end
|
||||
154
lib/common/collections/wp_items/detectable.rb
Executable file
154
lib/common/collections/wp_items/detectable.rb
Executable file
@@ -0,0 +1,154 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItems < Array
|
||||
|
||||
module Detectable
|
||||
|
||||
# The default request parameters
|
||||
def request_params; { cache_ttl: 0, followlocation: true } end
|
||||
|
||||
# options:
|
||||
# option name - default - description
|
||||
# show_progress - false - Output a progress bar
|
||||
# only_vulnerable - nil - Only check for vulnerable items
|
||||
# exclude_content - nil -
|
||||
def aggressive_detection(wp_target, options = {})
|
||||
queue_count = 0
|
||||
request_count = 0
|
||||
browser = Browser.instance
|
||||
hydra = browser.hydra
|
||||
targets = targets_items(wp_target, options)
|
||||
targets_size = targets.size
|
||||
show_progression = options[:show_progression] || false
|
||||
exist_options = {
|
||||
error_404_hash: wp_target.error_404_hash,
|
||||
homepage_hash: wp_target.homepage_hash,
|
||||
exclude_content: options[:exclude_content] ? %r{#{options[:exclude_content]}} : nil
|
||||
}
|
||||
|
||||
# If we only want the vulnerable ones, the passive detection is ignored
|
||||
# Otherwise, a passive detection is performed, and results will be merged
|
||||
results = options[:only_vulnerable] ? new : passive_detection(wp_target, options)
|
||||
|
||||
targets.each do |target_item|
|
||||
request = browser.forge_request(target_item.url, request_params)
|
||||
request_count += 1
|
||||
|
||||
request.on_complete do |response|
|
||||
|
||||
print "\rChecking for #{targets_size} total ... #{(request_count * 100) / targets_size}% complete." if show_progression
|
||||
|
||||
if target_item.exists?(exist_options, response)
|
||||
if !results.include?(target_item)
|
||||
results << target_item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hydra.queue(request)
|
||||
queue_count += 1
|
||||
|
||||
if queue_count == browser.max_threads
|
||||
hydra.run
|
||||
queue_count = 0
|
||||
end
|
||||
end
|
||||
|
||||
hydra.run
|
||||
results.sort!
|
||||
results # can't just return results.sort because the #sort returns an array, and we want a WpItems
|
||||
end
|
||||
|
||||
def passive_detection(wp_target, options = {})
|
||||
results = new
|
||||
item_class = self.item_class
|
||||
type = self.to_s.gsub(/Wp/, '').downcase
|
||||
response = Browser.instance.get(wp_target.url)
|
||||
item_options = {
|
||||
wp_content_dir: wp_target.wp_content_dir,
|
||||
wp_plugins_dir: wp_target.wp_plugins_dir,
|
||||
vulns_file: vulns_file
|
||||
}
|
||||
|
||||
regex1 = %r{(?:[^=:]+)\s?(?:=|:)\s?(?:"|')[^"']+\\?/}
|
||||
regex2 = %r{\\?/}
|
||||
regex3 = %r{\\?/([^/\\"']+)\\?(?:/|"|')}
|
||||
|
||||
names = response.body.scan(/#{regex1}#{Regexp.escape(wp_target.wp_content_dir)}#{regex2}#{Regexp.escape(type)}#{regex3}/i)
|
||||
|
||||
names.flatten.uniq.each do |name|
|
||||
results << item_class.new(wp_target.uri, item_options.merge(name: name))
|
||||
end
|
||||
|
||||
results.sort!
|
||||
results
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def targets_items(wp_target, options = {})
|
||||
item_class = self.item_class
|
||||
vulns_file = self.vulns_file
|
||||
|
||||
targets = vulnerable_targets_items(wp_target, item_class, vulns_file)
|
||||
|
||||
unless options[:only_vulnerable]
|
||||
unless options[:file]
|
||||
raise 'A file must be supplied'
|
||||
end
|
||||
|
||||
targets += targets_items_from_file(options[:file], wp_target, item_class, vulns_file)
|
||||
end
|
||||
|
||||
targets.uniq! { |t| t.name }
|
||||
targets.sort_by { rand }
|
||||
end
|
||||
|
||||
def vulnerable_targets_items(wp_target, item_class, vulns_file)
|
||||
targets = []
|
||||
xml = xml(vulns_file)
|
||||
|
||||
xml.xpath(item_xpath).each do |node|
|
||||
targets << create_item(
|
||||
item_class,
|
||||
node.attribute('name').text,
|
||||
wp_target,
|
||||
vulns_file
|
||||
)
|
||||
end
|
||||
targets
|
||||
end
|
||||
|
||||
def create_item(klass, name, wp_target, vulns_file = nil)
|
||||
klass.new(
|
||||
wp_target.uri,
|
||||
name: name,
|
||||
vulns_file: vulns_file,
|
||||
wp_content_dir: wp_target.wp_content_dir,
|
||||
wp_plugins_dir: wp_target.wp_plugins_dir
|
||||
)
|
||||
end
|
||||
|
||||
def targets_items_from_file(file, wp_target, item_class, vulns_file)
|
||||
targets = []
|
||||
|
||||
File.open(file, 'r') do |f|
|
||||
f.readlines.collect do |item_name|
|
||||
targets << create_item(
|
||||
item_class,
|
||||
item_name.strip,
|
||||
wp_target,
|
||||
vulns_file
|
||||
)
|
||||
end
|
||||
end
|
||||
targets
|
||||
end
|
||||
|
||||
# return class
|
||||
def item_class
|
||||
Object.const_get(self.to_s.gsub(/.$/, ''))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
11
lib/common/collections/wp_items/output.rb
Normal file
11
lib/common/collections/wp_items/output.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItems < Array
|
||||
module Output
|
||||
|
||||
def output
|
||||
self.each { |item| item.output }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
8
lib/common/collections/wp_plugins.rb
Executable file
8
lib/common/collections/wp_plugins.rb
Executable file
@@ -0,0 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_plugins/detectable'
|
||||
|
||||
class WpPlugins < WpItems
|
||||
extend WpPlugins::Detectable
|
||||
|
||||
end
|
||||
18
lib/common/collections/wp_plugins/detectable.rb
Normal file
18
lib/common/collections/wp_plugins/detectable.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpPlugins < WpItems
|
||||
module Detectable
|
||||
|
||||
def vulns_file
|
||||
unless @vulns_file
|
||||
@vulns_file = PLUGINS_VULNS_FILE
|
||||
end
|
||||
@vulns_file
|
||||
end
|
||||
|
||||
def item_xpath
|
||||
'//plugin'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
8
lib/common/collections/wp_themes.rb
Executable file
8
lib/common/collections/wp_themes.rb
Executable file
@@ -0,0 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_themes/detectable'
|
||||
|
||||
class WpThemes < WpItems
|
||||
extend WpThemes::Detectable
|
||||
|
||||
end
|
||||
18
lib/common/collections/wp_themes/detectable.rb
Normal file
18
lib/common/collections/wp_themes/detectable.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpThemes < WpItems
|
||||
module Detectable
|
||||
|
||||
def vulns_file
|
||||
unless @vulns_file
|
||||
@vulns_file = THEMES_VULNS_FILE
|
||||
end
|
||||
@vulns_file
|
||||
end
|
||||
|
||||
def item_xpath
|
||||
'//theme'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
8
lib/common/collections/wp_timthumbs.rb
Executable file
8
lib/common/collections/wp_timthumbs.rb
Executable file
@@ -0,0 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_timthumbs/detectable'
|
||||
|
||||
class WpTimthumbs < WpItems
|
||||
extend WpTimthumbs::Detectable
|
||||
|
||||
end
|
||||
56
lib/common/collections/wp_timthumbs/detectable.rb
Normal file
56
lib/common/collections/wp_timthumbs/detectable.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTimthumbs < WpItems
|
||||
module Detectable
|
||||
|
||||
# No passive detection
|
||||
# @return [ WpTimthumbs ]
|
||||
def passive_detection(wp_target, topns = {})
|
||||
new
|
||||
end
|
||||
|
||||
def targets_items(wp_target, options = {})
|
||||
unless options[:file]
|
||||
raise 'A file must be supplied'
|
||||
end
|
||||
|
||||
targets = options[:theme_name] ? theme_timthumbs(options[:theme_name], wp_target) : []
|
||||
|
||||
File.open(options[:file], 'r') do |f|
|
||||
f.readlines.collect do |path|
|
||||
targets << create_item(wp_target, path.strip)
|
||||
end
|
||||
end
|
||||
|
||||
targets.uniq { |i| i.url }
|
||||
end
|
||||
|
||||
# @return [ WpTimthumb Array ]
|
||||
def theme_timthumbs(theme_name, wp_target)
|
||||
targets = []
|
||||
wp_timthumb = create_item(wp_target)
|
||||
|
||||
%w{
|
||||
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
|
||||
}.each do |path|
|
||||
wp_timthumb.path = "$wp-content$/themes/#{theme_name}/#{path}"
|
||||
|
||||
targets << wp_timthumb.dup
|
||||
end
|
||||
targets
|
||||
end
|
||||
|
||||
# @return [ WpTimthumb ]
|
||||
def create_item(wp_target, path = nil)
|
||||
options = {
|
||||
wp_content_dir: wp_target.wp_content_dir,
|
||||
wp_plugins_dir: wp_target.wp_plugins_dir
|
||||
}
|
||||
|
||||
options.merge!(path: path) if path
|
||||
|
||||
WpTimthumb.new(wp_target.uri, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
10
lib/common/collections/wp_users.rb
Executable file
10
lib/common/collections/wp_users.rb
Executable file
@@ -0,0 +1,10 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_users/detectable'
|
||||
require 'common/collections/wp_users/output'
|
||||
|
||||
class WpUsers < WpItems
|
||||
extend WpUsers::Detectable
|
||||
include WpUsers::Output
|
||||
|
||||
end
|
||||
27
lib/common/collections/wp_users/detectable.rb
Executable file
27
lib/common/collections/wp_users/detectable.rb
Executable file
@@ -0,0 +1,27 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUsers < WpItems
|
||||
module Detectable
|
||||
|
||||
def request_params; {} end
|
||||
|
||||
# options:
|
||||
# :range - default 1..10
|
||||
def targets_items(wp_target, options = {})
|
||||
range = options[:range] || (1..10)
|
||||
targets = []
|
||||
|
||||
range.each do |user_id|
|
||||
targets << WpUser.new(wp_target.uri, id: user_id)
|
||||
end
|
||||
targets
|
||||
end
|
||||
|
||||
# No passive detection
|
||||
# @return [ WpUsers ]
|
||||
def passive_detection(wp_target, options = {})
|
||||
new
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
29
lib/common/collections/wp_users/output.rb
Normal file
29
lib/common/collections/wp_users/output.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUsers < WpItems
|
||||
module Output
|
||||
|
||||
# TODO : create a generic method to output tabs
|
||||
def output(left_margin = '')
|
||||
max_id_length = self.sort { |a, b| a.id.to_s.length <=> b.id.to_s.length }.last.id.to_s.length
|
||||
max_login_length = self.sort { |a, b| a.login.length <=> b.login.length }.last.login.length
|
||||
max_display_name_length = self.sort { |a, b| a.display_name.length <=> b.display_name.length }.last.display_name.length
|
||||
|
||||
inner_space = 2
|
||||
id_length = (max_id_length + inner_space * 2) /2 *2
|
||||
login_length = max_login_length + inner_space * 2
|
||||
display_name_length = max_display_name_length + inner_space * 2
|
||||
|
||||
puts left_margin + '+' * (id_length + login_length + display_name_length + 4)
|
||||
puts left_margin + '|' + 'id'.center(id_length) + '|' + 'login'.center(login_length) + '|' + 'display name'.center(display_name_length) + '|'
|
||||
puts left_margin + '|' + '+' * (id_length + login_length + display_name_length + 2) + '|'
|
||||
|
||||
self.each do |u|
|
||||
puts left_margin + '|' + u.id.to_s.center(id_length) + '|' + u.login.center(login_length) + '|' + u.display_name.center(display_name_length) + '|'
|
||||
end
|
||||
|
||||
puts left_margin + '+' * (id_length + login_length + display_name_length + 4)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -26,7 +26,11 @@ WPSCAN_LIB_DIR = LIB_DIR + '/wpscan'
|
||||
WPSTOOLS_LIB_DIR = LIB_DIR + '/wpstools'
|
||||
UPDATER_LIB_DIR = LIB_DIR + '/updater'
|
||||
COMMON_LIB_DIR = LIB_DIR + '/common'
|
||||
MODELS_LIB_DIR = COMMON_LIB_DIR + '/models'
|
||||
COLLECTIONS_LIB_DIR = COMMON_LIB_DIR + '/collections'
|
||||
|
||||
LOG_FILE = ROOT_DIR + '/log.txt'
|
||||
|
||||
# Plugins directories
|
||||
COMMON_PLUGINS_DIR = COMMON_LIB_DIR + '/plugins'
|
||||
WPSCAN_PLUGINS_DIR = WPSCAN_LIB_DIR + '/plugins' # Not used ATM
|
||||
@@ -49,6 +53,7 @@ LOCAL_FILES_XSD = DATA_DIR + '/local_vulnerable_files.xsd'
|
||||
WPSCAN_VERSION = '2.1'
|
||||
|
||||
$LOAD_PATH.unshift(LIB_DIR)
|
||||
$LOAD_PATH.unshift(MODELS_LIB_DIR)
|
||||
|
||||
require 'environment'
|
||||
|
||||
@@ -75,31 +80,6 @@ def add_trailing_slash(url)
|
||||
url =~ /\/$/ ? url : "#{url}/"
|
||||
end
|
||||
|
||||
# Gets the string all elements in stringarray ends with
|
||||
def get_equal_string_end(stringarray = [''])
|
||||
already_found = ''
|
||||
looping = true
|
||||
counter = -1
|
||||
if stringarray.kind_of? Array and stringarray.length > 1
|
||||
base = stringarray[0]
|
||||
while looping
|
||||
character = base[counter, 1]
|
||||
stringarray.each do |s|
|
||||
if s[counter, 1] != character
|
||||
looping = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if looping == false or (counter * -1) > base.length
|
||||
break
|
||||
end
|
||||
already_found = "#{character if character}#{already_found}"
|
||||
counter -= 1
|
||||
end
|
||||
end
|
||||
already_found
|
||||
end
|
||||
|
||||
# loading the updater
|
||||
require_files_from_directory(UPDATER_LIB_DIR)
|
||||
@updater = UpdaterFactory.get_updater(ROOT_DIR)
|
||||
@@ -138,12 +118,6 @@ def green(text)
|
||||
colorize(text, 32)
|
||||
end
|
||||
|
||||
def get_metasploit_url(module_path)
|
||||
# remove leading slash
|
||||
module_path = module_path.sub(/^\//, '')
|
||||
"http://www.metasploit.com/modules/#{module_path}"
|
||||
end
|
||||
|
||||
def xml(file)
|
||||
Nokogiri::XML(File.open(file)) do |config|
|
||||
config.noblanks
|
||||
|
||||
26
lib/common/models/vulnerability.rb
Executable file
26
lib/common/models/vulnerability.rb
Executable file
@@ -0,0 +1,26 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'vulnerability/output'
|
||||
|
||||
class Vulnerability
|
||||
include Vulnerability::Output
|
||||
|
||||
attr_accessor :title, :references, :type, :metasploit_modules
|
||||
|
||||
def initialize(title, type, references, metasploit_modules = [])
|
||||
@title = title
|
||||
@type = type
|
||||
@references = references
|
||||
@metasploit_modules = metasploit_modules
|
||||
end
|
||||
|
||||
def self.load_from_xml_node(xml_node)
|
||||
new(
|
||||
xml_node.search('title').text,
|
||||
xml_node.search('type').text,
|
||||
xml_node.search('reference').map(&:text),
|
||||
xml_node.search('metasploit').map(&:text)
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
25
lib/common/models/vulnerability/output.rb
Normal file
25
lib/common/models/vulnerability/output.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class Vulnerability
|
||||
module Output
|
||||
|
||||
# output the vulnerability
|
||||
def output
|
||||
puts ' |'
|
||||
puts ' | ' + red("* Title: #{title}")
|
||||
references.each do |r|
|
||||
puts ' | ' + red("* Reference: #{r}")
|
||||
end
|
||||
metasploit_modules.each do |m|
|
||||
puts ' | ' + red("* Metasploit module: #{metasploit_module_url(m)}")
|
||||
end
|
||||
end
|
||||
|
||||
def self.metasploit_module_url(module_path)
|
||||
# remove leading slash
|
||||
module_path = module_path.sub(/^\//, '')
|
||||
"http://www.metasploit.com/modules/#{module_path}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
80
lib/common/models/wp_item.rb
Executable file
80
lib/common/models/wp_item.rb
Executable file
@@ -0,0 +1,80 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_item/findable'
|
||||
require 'wp_item/versionable'
|
||||
require 'wp_item/vulnerable'
|
||||
require 'wp_item/existable'
|
||||
require 'wp_item/infos'
|
||||
require 'wp_item/output'
|
||||
|
||||
class WpItem
|
||||
|
||||
extend WpItem::Findable
|
||||
include WpItem::Versionable
|
||||
include WpItem::Vulnerable
|
||||
include WpItem::Existable
|
||||
include WpItem::Infos
|
||||
include WpItem::Output
|
||||
|
||||
attr_reader :path
|
||||
attr_accessor :name, :wp_content_dir, :wp_plugins_dir
|
||||
|
||||
def allowed_options
|
||||
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :vulns_file]
|
||||
end
|
||||
|
||||
# options :
|
||||
# See allowed_options
|
||||
def initialize(target_base_uri, options = {})
|
||||
|
||||
options[:wp_content_dir] ||= 'wp-content'
|
||||
options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins'
|
||||
|
||||
set_options(options)
|
||||
forge_uri(target_base_uri)
|
||||
end
|
||||
|
||||
def set_options(options)
|
||||
allowed_options.each do |allowed_option|
|
||||
if options.has_key?(allowed_option)
|
||||
method = :"#{allowed_option}="
|
||||
|
||||
if self.respond_to?(method)
|
||||
self.send(method, options[allowed_option])
|
||||
else
|
||||
raise "#{self.class} does not respond to #{method}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
private :set_options
|
||||
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri
|
||||
end
|
||||
|
||||
def uri
|
||||
return path ? @uri.merge(path) : @uri
|
||||
end
|
||||
|
||||
def url; uri.to_s end
|
||||
|
||||
def path=(path)
|
||||
@path = URI.encode(
|
||||
path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
|
||||
)
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
name <=> other.name
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
name === other.name
|
||||
end
|
||||
|
||||
def ===(other)
|
||||
self == other && version === other.version
|
||||
end
|
||||
|
||||
end
|
||||
55
lib/common/models/wp_item/existable.rb
Executable file
55
lib/common/models/wp_item/existable.rb
Executable file
@@ -0,0 +1,55 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
# HACK
|
||||
module Typhoeus
|
||||
class Response
|
||||
|
||||
# Compare the body hash to error_404_hash and homepage_hash
|
||||
# returns true if they are different, false otherwise
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def has_valid_hash?(error_404_hash, homepage_hash)
|
||||
body_hash = Digest::MD5.hexdigest(self.body)
|
||||
|
||||
body_hash != error_404_hash && body_hash != homepage_hash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class WpItem
|
||||
module Existable
|
||||
|
||||
def exists?(options = {}, response = nil)
|
||||
unless response
|
||||
response = Browser.instance.get(url)
|
||||
end
|
||||
exists_from_response?(response, options)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# options:
|
||||
# :error_404_hash
|
||||
# :homepage_hash
|
||||
# :exclude_content REGEXP
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists_from_response?(response, options = {})
|
||||
# FIXME : The response is supposed to follow locations, so we should not have 301 or 302.
|
||||
# However, due to an issue with Typhoeus or Webmock, the location is not followed in specs
|
||||
if [200, 301, 302, 401, 403].include?(response.code)
|
||||
if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash])
|
||||
if options[:exclude_content]
|
||||
unless response.body.match(options[:exclude_content])
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
15
lib/common/models/wp_item/findable.rb
Executable file
15
lib/common/models/wp_item/findable.rb
Executable file
@@ -0,0 +1,15 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
attr_reader :found_from
|
||||
|
||||
#def allowed_options; super << :found_from end
|
||||
|
||||
def found_from=(method)
|
||||
@found_from = method[%r{find_from_(.*)}, 1].gsub('_', ' ')
|
||||
end
|
||||
|
||||
module Findable
|
||||
|
||||
end
|
||||
end
|
||||
58
lib/common/models/wp_item/infos.rb
Normal file
58
lib/common/models/wp_item/infos.rb
Normal file
@@ -0,0 +1,58 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
module Infos
|
||||
|
||||
# @return [ Boolean ]
|
||||
def has_readme?
|
||||
Browser.instance.get(readme_url).code == 200 ? true : false
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def readme_url
|
||||
@uri.merge('readme.txt').to_s
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def wordpress_url
|
||||
|
||||
end
|
||||
|
||||
def wordpress_org_item?
|
||||
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
def has_changelog?
|
||||
Browser.instance.get(changelog_url).code == 200 ? true : false
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def changelog_url
|
||||
@uri.merge('changelog.txt').to_s
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
def has_directory_listing?
|
||||
Browser.instance.get(@uri.to_s).body[%r{<title>Index of}] ? true : false
|
||||
end
|
||||
|
||||
# Discover any error_log files created by WordPress
|
||||
# These are created by the WordPress error_log() function
|
||||
# They are normally found in the /plugins/ directory,
|
||||
# however can also be found in their specific plugin dir.
|
||||
# http://www.exploit-db.com/ghdb/3714/
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def has_error_log?
|
||||
response_body = Browser.instance.get(error_log_url, headers: {'range' => 'bytes=0-700'}).body
|
||||
response_body[%r{PHP Fatal error}i] ? true : false
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def error_log_url
|
||||
@uri.merge('error_log').to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
24
lib/common/models/wp_item/output.rb
Normal file
24
lib/common/models/wp_item/output.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
module Output
|
||||
|
||||
# @return [ Void ]
|
||||
def output
|
||||
puts
|
||||
puts " | Name: #{self}" #this will also output the version number if detected
|
||||
puts " | Location: #{url}"
|
||||
#puts " | WordPress: #{wordpress_url}" if wordpress_org_item?
|
||||
puts ' | Directory listing enabled: Yes' if has_directory_listing?
|
||||
puts " | Readme: #{readme_url}" if has_readme?
|
||||
puts " | Changelog: #{changelog_url}" if has_changelog?
|
||||
|
||||
vulnerabilities.output
|
||||
|
||||
if has_error_log?
|
||||
puts ' | ' + red('[!]') + " An error_log file has been found : #{error_log_url}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
25
lib/common/models/wp_item/versionable.rb
Executable file
25
lib/common/models/wp_item/versionable.rb
Executable file
@@ -0,0 +1,25 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
attr_writer :version
|
||||
|
||||
#def allowed_options; super << :version end
|
||||
|
||||
module Versionable
|
||||
|
||||
# Get the version from the readme.txt
|
||||
def version
|
||||
unless @version
|
||||
response = Browser.instance.get(readme_url)
|
||||
@version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1]
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
def to_s
|
||||
item_version = self.version
|
||||
"#@name#{' v' + item_version.strip if item_version}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
26
lib/common/models/wp_item/vulnerable.rb
Executable file
26
lib/common/models/wp_item/vulnerable.rb
Executable file
@@ -0,0 +1,26 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
|
||||
# moved this into the module ?
|
||||
def vulns_file=(file)
|
||||
if File.exists?(file)
|
||||
@vulns_file = file
|
||||
else
|
||||
raise "The file #{file} does not exist"
|
||||
end
|
||||
end
|
||||
|
||||
module Vulnerable
|
||||
# @return [ Vulnerabilities ]
|
||||
def vulnerabilities
|
||||
xml = xml(vulns_file)
|
||||
vulnerabilities = Vulnerabilities.new
|
||||
|
||||
xml.xpath(vulns_xpath).each do |node|
|
||||
vulnerabilities << Vulnerability.load_from_xml_node(node)
|
||||
end
|
||||
vulnerabilities
|
||||
end
|
||||
end
|
||||
end
|
||||
10
lib/common/models/wp_plugin.rb
Executable file
10
lib/common/models/wp_plugin.rb
Executable file
@@ -0,0 +1,10 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpPlugin < WpItem
|
||||
include WpPlugin::Vulnerable
|
||||
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri.merge(URI.encode(wp_plugins_dir) + '/' + URI.encode(name) + '/')
|
||||
end
|
||||
|
||||
end
|
||||
20
lib/common/models/wp_plugin/vulnerable.rb
Normal file
20
lib/common/models/wp_plugin/vulnerable.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpPlugin < WpItem
|
||||
|
||||
def vulns_file
|
||||
unless @vulns_file
|
||||
@vulns_file = PLUGINS_VULNS_FILE
|
||||
end
|
||||
@vulns_file
|
||||
end
|
||||
|
||||
def vulns_xpath
|
||||
"//plugin[@name='#{@name}']/vulnerability"
|
||||
end
|
||||
|
||||
module Vulnerable
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
26
lib/common/models/wp_theme.rb
Executable file
26
lib/common/models/wp_theme.rb
Executable file
@@ -0,0 +1,26 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_theme/findable'
|
||||
require 'wp_theme/versionable'
|
||||
|
||||
class WpTheme < WpItem
|
||||
extend WpTheme::Findable
|
||||
include WpTheme::Versionable
|
||||
include WpTheme::Vulnerable
|
||||
|
||||
attr_writer :style_url
|
||||
|
||||
def allowed_options; super << :style_url end
|
||||
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri.merge(URI.encode(wp_content_dir + '/themes/' + name + '/')) # make suer that this last / is present (spec)
|
||||
end
|
||||
|
||||
def style_url
|
||||
unless @style_url
|
||||
@style_url = uri.merge('style.css').to_s
|
||||
end
|
||||
@style_url
|
||||
end
|
||||
|
||||
end
|
||||
60
lib/common/models/wp_theme/findable.rb
Executable file
60
lib/common/models/wp_theme/findable.rb
Executable file
@@ -0,0 +1,60 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Findable
|
||||
|
||||
# Find the main theme of the blog
|
||||
# returns a WpTheme object or nil
|
||||
def find(target_uri)
|
||||
methods.grep(/find_from_/).each do |method|
|
||||
if wp_theme = self.send(method, target_uri)
|
||||
wp_theme.found_from = method
|
||||
|
||||
return wp_theme
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
# Discover the wordpress theme name by parsing the css link rel
|
||||
def find_from_css_link(target_uri)
|
||||
response = Browser.instance.get_and_follow_location(target_uri.to_s)
|
||||
|
||||
# https + domain is optional because of relative links
|
||||
matches = %r{(?:https?://[^"']+)?/([^/]+)/themes/([^"']+)/style.css}i.match(response.body)
|
||||
if matches
|
||||
return new(
|
||||
target_uri,
|
||||
{
|
||||
name: matches[2],
|
||||
style_url: matches[0],
|
||||
wp_content_dir: matches[1]
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# http://code.google.com/p/wpscan/issues/detail?id=141
|
||||
def find_from_wooframework(target_uri)
|
||||
body = Browser.instance.get(target_uri.to_s).body
|
||||
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
|
||||
|
||||
matches = regexp.match(body)
|
||||
if matches
|
||||
woo_theme_name = matches[1]
|
||||
woo_theme_version = matches[2]
|
||||
woo_framework_version = matches[3] # Not used at this time
|
||||
|
||||
return new(
|
||||
target_uri,
|
||||
{
|
||||
name: woo_theme_name,
|
||||
version: woo_theme_version
|
||||
#path: woo_theme_name
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
19
lib/common/models/wp_theme/versionable.rb
Executable file
19
lib/common/models/wp_theme/versionable.rb
Executable file
@@ -0,0 +1,19 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Versionable
|
||||
|
||||
def version
|
||||
unless @version
|
||||
@version = Browser.instance.get(style_url).body[%r{Version:\s([^\s]+)}i, 1]
|
||||
|
||||
# Get Version from readme.txt
|
||||
unless @version
|
||||
@version = super
|
||||
end
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
20
lib/common/models/wp_theme/vulnerable.rb
Normal file
20
lib/common/models/wp_theme/vulnerable.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
|
||||
def vulns_file
|
||||
unless @vulns_file
|
||||
@vulns_file = THEMES_VULNS_FILE
|
||||
end
|
||||
@vulns_file
|
||||
end
|
||||
|
||||
def vulns_xpath
|
||||
"//theme[@name='#{@name}']/vulnerability"
|
||||
end
|
||||
|
||||
module Vulnerable
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
12
lib/common/models/wp_timthumb.rb
Executable file
12
lib/common/models/wp_timthumb.rb
Executable file
@@ -0,0 +1,12 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_timthumb/versionable'
|
||||
require 'wp_timthumb/existable'
|
||||
require 'wp_timthumb/output'
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
include WpTimthumb::Versionable
|
||||
include WpTimthumb::Existable
|
||||
include WpTimthumb::Output
|
||||
|
||||
end
|
||||
11
lib/common/models/wp_timthumb/existable.rb
Normal file
11
lib/common/models/wp_timthumb/existable.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
module Existable
|
||||
|
||||
def exists_from_response?(response, options = {})
|
||||
response.code == 400 && response.body =~ /no image specified/i ? true : false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
11
lib/common/models/wp_timthumb/output.rb
Normal file
11
lib/common/models/wp_timthumb/output.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
module Output
|
||||
|
||||
def output
|
||||
puts ' | ' + red('[!]') + " #{url}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
13
lib/common/models/wp_timthumb/versionable.rb
Executable file
13
lib/common/models/wp_timthumb/versionable.rb
Executable file
@@ -0,0 +1,13 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
module Versionable
|
||||
|
||||
# Get the version from the body of an invalid request
|
||||
# See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426
|
||||
def version
|
||||
response = Browser.instance.get(url)
|
||||
response.body[%r{TimThumb version\s*: ([^<]+)} , 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
33
lib/common/models/wp_user.rb
Executable file
33
lib/common/models/wp_user.rb
Executable file
@@ -0,0 +1,33 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_user/existable'
|
||||
|
||||
class WpUser < WpItem
|
||||
|
||||
include WpUser::Existable
|
||||
|
||||
attr_accessor :id, :login, :display_name, :password
|
||||
|
||||
def allowed_options; [:id, :login, :display_name, :password] end
|
||||
|
||||
def uri
|
||||
if id
|
||||
return @uri.merge("?author=#{id}")
|
||||
else
|
||||
raise 'The id is nil'
|
||||
end
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
id <=> other.id
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self === (other)
|
||||
end
|
||||
|
||||
def ===(other)
|
||||
id === other.id && login === other.login
|
||||
end
|
||||
|
||||
end
|
||||
51
lib/common/models/wp_user/existable.rb
Executable file
51
lib/common/models/wp_user/existable.rb
Executable file
@@ -0,0 +1,51 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUser < WpItem
|
||||
module Existable
|
||||
|
||||
def exists_from_response?(response, options = {})
|
||||
load_login_from_response(response)
|
||||
|
||||
@login ? true : false
|
||||
end
|
||||
|
||||
def load_login_from_response(response)
|
||||
if response.code == 301 # login in location?
|
||||
location = response.headers_hash['Location']
|
||||
|
||||
@login = WpUser::Existable.login_from_author_pattern(location)
|
||||
@display_name = WpUser::Existable.display_name_from_body(
|
||||
Browser.instance.get(location).body
|
||||
)
|
||||
elsif response.code == 200 # login in body?
|
||||
@login = WpUser::Existable.login_from_body(response.body)
|
||||
@display_name = WpUser::Existable.display_name_from_body(response.body)
|
||||
end
|
||||
end
|
||||
|
||||
def self.login_from_author_pattern(text)
|
||||
text[%r{/author/([^/\b]+)/?}i, 1]
|
||||
end
|
||||
|
||||
def self.login_from_body(body)
|
||||
# Feed URL with Permalinks
|
||||
login = WpUser::Existable.login_from_author_pattern(body)
|
||||
|
||||
unless login
|
||||
# No Permalinks
|
||||
login = body[%r{<body class="archive author author-([^\s]+) author-(\d+)}i, 1]
|
||||
end
|
||||
|
||||
login
|
||||
end
|
||||
|
||||
def self.display_name_from_body(body)
|
||||
if title_tag = body[%r{<title>([^<]+)</title>}i, 1]
|
||||
title_tag.sub!('|', '|')
|
||||
|
||||
return title_tag[%r{([^|]+) }, 1]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
32
lib/common/models/wp_version.rb
Executable file
32
lib/common/models/wp_version.rb
Executable file
@@ -0,0 +1,32 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_version/findable'
|
||||
require 'wp_version/vulnerable'
|
||||
require 'wp_version/output'
|
||||
|
||||
class WpVersion < WpItem
|
||||
|
||||
extend WpVersion::Findable
|
||||
include WpVersion::Vulnerable
|
||||
include WpVersion::Output
|
||||
|
||||
@@version_xml =
|
||||
|
||||
# The version number
|
||||
attr_accessor :number
|
||||
|
||||
def allowed_options; super << :number << :found_from end
|
||||
|
||||
def self.version_xml
|
||||
@@version_xml
|
||||
end
|
||||
|
||||
def self.version_xml=(xml)
|
||||
if File.exists?(xml)
|
||||
@@version_xml = xml
|
||||
else
|
||||
raise "The file #{xml} does not exist"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
162
lib/common/models/wp_version/findable.rb
Executable file
162
lib/common/models/wp_version/findable.rb
Executable file
@@ -0,0 +1,162 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpVersion < WpItem
|
||||
module Findable
|
||||
|
||||
# Find the version of the wp_target blog
|
||||
# returns a WpVersion object or nil
|
||||
def find(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
methods.grep(/find_from_/).each do |method|
|
||||
if version = send(method, target_uri, wp_content_dir, wp_plugins_dir)
|
||||
|
||||
return new(target_uri, number: version, found_from: method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the first match of <pattern> in the body of the url
|
||||
def scan_url(target_uri, pattern, path = nil)
|
||||
url = path ? target_uri.merge(path).to_s : target_uri.to_s
|
||||
response = Browser.instance.get_and_follow_location(url)
|
||||
|
||||
response.body[pattern, 1]
|
||||
end
|
||||
|
||||
#
|
||||
# DO NOT Change the order of the following methods
|
||||
# unless you know what you are doing
|
||||
# See WpVersion.find
|
||||
#
|
||||
|
||||
# Attempts to find the wordpress version from,
|
||||
# the generator meta tag in the html source.
|
||||
#
|
||||
# The meta tag can be removed however it seems,
|
||||
# that it is reinstated on upgrade.
|
||||
def find_from_meta_generator(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{name="generator" content="wordpress #{version_pattern}"}i
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS feed source.
|
||||
def find_from_rss_generator(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<generator>http://wordpress.org/\?v=#{version_pattern}</generator>}i,
|
||||
'feed/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find WordPress version from,
|
||||
# the generator tag in the RDF feed source.
|
||||
def find_from_rdf_generator(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{version_pattern}" />}i,
|
||||
'feed/rdf/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS2 feed source.
|
||||
#
|
||||
# Have not been able to find an example of this - Ryan
|
||||
#def find_from_rss2_generator(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
# scan_url(
|
||||
# target_uri,
|
||||
# %r{<generator>http://wordpress.org/?v=(#{WpVersion.version_pattern})</generator>}i,
|
||||
# 'feed/rss/'
|
||||
# )
|
||||
#end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the Atom source.
|
||||
def find_from_atom_generator(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<generator uri="http://wordpress.org/" version="#{version_pattern}">WordPress</generator>}i,
|
||||
'feed/atom/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the comment rss source.
|
||||
#
|
||||
# Have not been able to find an example of this - Ryan
|
||||
#def find_from_comments_rss_generator(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
# scan_url(
|
||||
# target_uri,
|
||||
# %r{<!-- generator="WordPress/#{WpVersion.version_pattern}" -->}i,
|
||||
# 'comments/feed/'
|
||||
# )
|
||||
#end
|
||||
|
||||
# Uses data/wp_versions.xml to try to identify a
|
||||
# wordpress version.
|
||||
#
|
||||
# It does this by using client side file hashing
|
||||
#
|
||||
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
|
||||
#
|
||||
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
xml = xml(version_xml)
|
||||
# This wp_item will take care of encoding the path
|
||||
# and replace variables like $wp-content$ and $wp-plugins$
|
||||
wp_item = WpItem.new(target_uri,
|
||||
wp_content_dir: wp_content_dir,
|
||||
wp_plugins_dir: wp_plugins_dir)
|
||||
|
||||
xml.xpath('//file').each do |node|
|
||||
wp_item.path = node.attribute('src').text
|
||||
|
||||
response = Browser.instance.get(wp_item.url)
|
||||
md5sum = Digest::MD5.hexdigest(response.body)
|
||||
|
||||
node.search('hash').each do |hash|
|
||||
if hash.attribute('md5').text == md5sum
|
||||
return hash.search('version').text
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the readme.html file.
|
||||
def find_from_readme(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<br />\sversion #{version_pattern}}i,
|
||||
'readme.html'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the sitemap.xml file.
|
||||
#
|
||||
# See: http://code.google.com/p/wpscan/issues/detail?id=109
|
||||
def find_from_sitemap_generator(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{generator="wordpress/#{version_pattern}"}i,
|
||||
'sitemap.xml'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the p-links-opml.php file.
|
||||
def find_from_links_opml(target_uri, wp_content_dir, wp_plugins_dir)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{generator="wordpress/#{version_pattern}"}i,
|
||||
'wp-links-opml.php'
|
||||
)
|
||||
end
|
||||
|
||||
# Used to check if the version is correct: must contain at least one dot.
|
||||
def version_pattern
|
||||
'([^\r\n"\']+\.[^\r\n"\']+)'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
20
lib/common/models/wp_version/output.rb
Normal file
20
lib/common/models/wp_version/output.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpVersion < WpItem
|
||||
module Output
|
||||
|
||||
def output
|
||||
puts green('[+]') + " WordPress version #{self.number} identified from #{self.found_from}"
|
||||
|
||||
vulnerabilities = self.vulnerabilities
|
||||
|
||||
unless vulnerabilities.empty?
|
||||
puts
|
||||
puts red('[!]') + " We have identified #{vulnerabilities.size} vulnerabilities from the version number :"
|
||||
|
||||
vulnerabilities.output
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
19
lib/common/models/wp_version/vulnerable.rb
Normal file
19
lib/common/models/wp_version/vulnerable.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpVersion < WpItem
|
||||
|
||||
def vulns_file
|
||||
unless @vulns_file
|
||||
@vulns_file = WP_VULNS_FILE
|
||||
end
|
||||
@vulns_file
|
||||
end
|
||||
|
||||
def vulns_xpath
|
||||
"//wordpress[@version='#{@number}']/vulnerability"
|
||||
end
|
||||
|
||||
module Vulnerable
|
||||
|
||||
end
|
||||
end
|
||||
@@ -27,7 +27,6 @@ end
|
||||
|
||||
begin
|
||||
# Standard libs
|
||||
require 'rubygems'
|
||||
require 'bundler/setup'
|
||||
require 'getoptlong'
|
||||
require 'optparse' # Will replace getoptlong
|
||||
|
||||
@@ -19,18 +19,18 @@
|
||||
|
||||
module BruteForce
|
||||
|
||||
# param array of string logins
|
||||
# param array of WpUsers wp_users
|
||||
# param string wordlist_path
|
||||
# param hash options
|
||||
# boolean :show_progression If true, will output the details (Sucess, error etc)
|
||||
def brute_force(logins, wordlist_path, options = {})
|
||||
def brute_force(wp_users, wordlist_path, options = {})
|
||||
hydra = Browser.instance.hydra
|
||||
number_of_passwords = BruteForce.lines_in_file(wordlist_path)
|
||||
login_url = login_url()
|
||||
found = []
|
||||
show_progression = options[:show_progression] || false
|
||||
|
||||
logins.each do |login|
|
||||
wp_users.each do |wp_user|
|
||||
queue_count = 0
|
||||
request_count = 0
|
||||
password_found = false
|
||||
@@ -46,14 +46,14 @@ module BruteForce
|
||||
queue_count += 1
|
||||
|
||||
# create local vars for on_complete call back, Issue 51.
|
||||
username = login.name != 'empty' ? login.name : login.nickname # Issue #66
|
||||
login = wp_user.login
|
||||
password = password
|
||||
|
||||
# the request object
|
||||
request = Browser.instance.forge_request(login_url,
|
||||
{
|
||||
method: :post,
|
||||
body: { log: URI::encode(username), pwd: URI::encode(password) },
|
||||
body: { log: URI::encode(login), pwd: URI::encode(password) },
|
||||
cache_ttl: 0
|
||||
}
|
||||
)
|
||||
@@ -61,13 +61,13 @@ module BruteForce
|
||||
# tell hydra what to do when the request completes
|
||||
request.on_complete do |response|
|
||||
|
||||
puts "\n Trying Username : #{username} Password : #{password}" if @verbose
|
||||
puts "\n Trying Username : #{login} Password : #{password}" if @verbose
|
||||
|
||||
if response.body =~ /login_error/i
|
||||
puts "\nIncorrect username and/or password." if @verbose
|
||||
puts "\nIncorrect login and/or password." if @verbose
|
||||
elsif response.code == 302
|
||||
puts "\n " + green('[SUCCESS]') + " Username : #{username} Password : #{password}\n" if show_progression
|
||||
found << { name: username, password: password }
|
||||
puts "\n " + green('[SUCCESS]') + " Login : #{login} Password : #{password}\n" if show_progression
|
||||
found << { name: login, password: password }
|
||||
password_found = true
|
||||
elsif response.timed_out?
|
||||
puts red('ERROR:') + ' Request timed out.' if show_progression
|
||||
@@ -86,14 +86,14 @@ module BruteForce
|
||||
end
|
||||
end
|
||||
|
||||
# move onto the next username if we have found a valid password
|
||||
# move onto the next login if we have found a valid password
|
||||
break if password_found
|
||||
|
||||
# queue the request to be sent later
|
||||
hydra.queue(request)
|
||||
|
||||
# progress indicator
|
||||
print "\r Brute forcing user '#{username}' with #{number_of_passwords} passwords... #{(request_count * 100) / number_of_passwords}% complete." if show_progression
|
||||
print "\r Brute forcing user '#{login}' with #{number_of_passwords} passwords... #{(request_count * 100) / number_of_passwords}% complete." if show_progression
|
||||
|
||||
# it can take a long time to queue 2 million requests,
|
||||
# for that reason, we queue @threads, send @threads, queue @threads and so on.
|
||||
|
||||
@@ -38,10 +38,10 @@ module WpLoginProtection
|
||||
plugin_name = symbol_to_call[LOGIN_PROTECTION_METHOD_PATTERN, 1].gsub('_', '-')
|
||||
|
||||
return @login_protection_plugin = WpPlugin.new(
|
||||
@uri,
|
||||
name: plugin_name,
|
||||
base_url: @uri,
|
||||
path: "/plugins/#{plugin_name}/",
|
||||
wp_content_dir: @wp_content_dir
|
||||
wp_content_dir: wp_content_dir,
|
||||
wp_plugins_dir: wp_plugins_dir
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -54,38 +54,39 @@ module WpLoginProtection
|
||||
# Thanks to Alip Aswalid for providing this method.
|
||||
# http://wordpress.org/extend/plugins/login-lockdown/
|
||||
def has_login_lockdown_protection?
|
||||
Browser.instance.get(login_url()).body =~ %r{Login LockDown}i ? true : false
|
||||
Browser.instance.get(login_url).body =~ %r{Login LockDown}i ? true : false
|
||||
end
|
||||
|
||||
# http://wordpress.org/extend/plugins/login-lock/
|
||||
def has_login_lock_protection?
|
||||
Browser.instance.get(login_url()).body =~ %r{LOGIN LOCK} ? true : false
|
||||
Browser.instance.get(login_url).body =~ %r{LOGIN LOCK} ? true : false
|
||||
end
|
||||
|
||||
# http://wordpress.org/extend/plugins/better-wp-security/
|
||||
def has_better_wp_security_protection?
|
||||
Browser.instance.get(better_wp_security_url()).code != 404
|
||||
Browser.instance.get(better_wp_security_url).code != 404
|
||||
end
|
||||
|
||||
def plugin_url(plugin_name)
|
||||
WpPlugin.new(
|
||||
@uri,
|
||||
name: plugin_name,
|
||||
wp_content_dir: wp_content_dir,
|
||||
wp_plugins_dir: wp_plugins_dir
|
||||
).url
|
||||
end
|
||||
|
||||
def better_wp_security_url
|
||||
WpPlugin.new(wp_content_dir: @wp_content_dir,
|
||||
base_url: @uri,
|
||||
path: '/plugins/better-wp-security/',
|
||||
name: 'better-wp-security'
|
||||
).get_url_without_filename
|
||||
plugin_url('better-wp-security/')
|
||||
end
|
||||
|
||||
# http://wordpress.org/extend/plugins/simple-login-lockdown/
|
||||
def has_simple_login_lockdown_protection?
|
||||
Browser.instance.get(simple_login_lockdown_url()).code != 404
|
||||
Browser.instance.get(simple_login_lockdown_url).code != 404
|
||||
end
|
||||
|
||||
def simple_login_lockdown_url
|
||||
WpPlugin.new(wp_content_dir: @wp_content_dir,
|
||||
base_url: @uri,
|
||||
path: '/plugins/simple-login-lockdown/',
|
||||
name: 'simple-login-lockdown'
|
||||
).get_url_without_filename
|
||||
plugin_url('simple-login-lockdown/')
|
||||
end
|
||||
|
||||
# http://wordpress.org/extend/plugins/login-security-solution/
|
||||
@@ -94,36 +95,24 @@ module WpLoginProtection
|
||||
end
|
||||
|
||||
def login_security_solution_url
|
||||
WpPlugin.new(wp_content_dir: @wp_content_dir,
|
||||
base_url: @uri,
|
||||
path: '/plugins/login-security-solution/',
|
||||
name: 'login-security-solution'
|
||||
).get_url_without_filename
|
||||
plugin_url('login-security-solution')
|
||||
end
|
||||
|
||||
# http://wordpress.org/extend/plugins/limit-login-attempts/
|
||||
def has_limit_login_attempts_protection?
|
||||
Browser.instance.get(limit_login_attempts_url()).code != 404
|
||||
Browser.instance.get(limit_login_attempts_url).code != 404
|
||||
end
|
||||
|
||||
def limit_login_attempts_url
|
||||
WpPlugin.new(wp_content_dir: @wp_content_dir,
|
||||
base_url: @uri,
|
||||
path: '/plugins/limit-login-attempts/',
|
||||
name: 'limit-login-attempts'
|
||||
).get_url_without_filename
|
||||
plugin_url('limit-login-attempts')
|
||||
end
|
||||
|
||||
# http://wordpress.org/extend/plugins/bluetrait-event-viewer/
|
||||
def has_bluetrait_event_viewer_protection?
|
||||
Browser.instance.get(bluetrait_event_viewer_url()).code != 404
|
||||
Browser.instance.get(bluetrait_event_viewer_url).code != 404
|
||||
end
|
||||
|
||||
def bluetrait_event_viewer_url
|
||||
WpPlugin.new(wp_content_dir: @wp_content_dir,
|
||||
base_url: @uri,
|
||||
path: '/plugins/bluetrait-event-viewer/',
|
||||
name: 'bluetrait-event-viewer'
|
||||
).get_url_without_filename
|
||||
plugin_url('bluetrait-event-viewer')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
module WpPlugins
|
||||
|
||||
# Enumerate installed plugins.
|
||||
#
|
||||
# return array of WpPlugin
|
||||
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[:vulns_xpath] = "//plugin[@name='#{@name}']/vulnerability"
|
||||
options[:vulns_xpath_2] = '//plugin'
|
||||
options[:type] = 'plugins'
|
||||
result = WpDetector.aggressive_detection(options)
|
||||
plugins = []
|
||||
result.each do |r|
|
||||
plugins << WpPlugin.new(
|
||||
base_url: r.base_url,
|
||||
path: r.path,
|
||||
wp_content_dir: r.wp_content_dir,
|
||||
name: r.name,
|
||||
type: 'plugins',
|
||||
wp_plugins_dir: r.wp_plugins_dir
|
||||
)
|
||||
end
|
||||
plugins.sort_by { |p| p.name }
|
||||
end
|
||||
|
||||
# http://code.google.com/p/wpscan/issues/detail?id=42
|
||||
# plugins can be found in the source code :
|
||||
# <script src='http://example.com/wp-content/plugins/s2member/...' />
|
||||
# <link rel='stylesheet' href='http://example.com/wp-content/plugins/wp-minify/..' type='text/css' media='screen'/>
|
||||
# ...
|
||||
# return array of WpPlugin
|
||||
def plugins_from_passive_detection(options)
|
||||
plugins = []
|
||||
temp = WpDetector.passive_detection(options[:base_url], 'plugins', options[:wp_content_dir])
|
||||
|
||||
temp.each do |item|
|
||||
plugins << WpPlugin.new(
|
||||
base_url: item.base_url,
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
wp_content_dir: options[:wp_content_dir],
|
||||
type: 'plugins',
|
||||
wp_plugins_dir: options[:wp_plugins_dir]
|
||||
)
|
||||
end
|
||||
plugins.sort_by { |p| p.name }
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,59 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
module WpThemes
|
||||
|
||||
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[:vulns_xpath] = "//theme[@name='#{@name}']/vulnerability"
|
||||
options[:vulns_xpath_2] = '//theme'
|
||||
options[:type] = 'themes'
|
||||
result = WpDetector.aggressive_detection(options)
|
||||
themes = []
|
||||
result.each do |r|
|
||||
themes << WpTheme.new(
|
||||
base_url: r.base_url,
|
||||
path: r.path,
|
||||
wp_content_dir: r.wp_content_dir,
|
||||
name: r.name
|
||||
)
|
||||
end
|
||||
themes.sort_by { |t| t.name }
|
||||
end
|
||||
|
||||
def themes_from_passive_detection(options)
|
||||
themes = []
|
||||
temp = WpDetector.passive_detection(options[:base_url], 'themes', options[:wp_content_dir])
|
||||
|
||||
temp.each do |item|
|
||||
themes << WpTheme.new(
|
||||
base_url: item.base_url,
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
wp_content_dir: options[:wp_content_dir]
|
||||
)
|
||||
end
|
||||
themes.sort_by { |t| t.name }
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,74 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
module WpTimthumbs
|
||||
|
||||
# Used as cache :
|
||||
# nil => timthumbs not checked,
|
||||
# [] => no timthumbs,
|
||||
# otherwise array of timthumbs url found
|
||||
@wp_timthumbs = nil
|
||||
|
||||
def has_timthumbs?(theme_name, options = {})
|
||||
!timthumbs(theme_name, options).empty?
|
||||
end
|
||||
|
||||
def timthumbs(theme_name = nil, options = {})
|
||||
if @wp_timthumbs.nil?
|
||||
options[:type] = 'timthumbs'
|
||||
options[:only_vulnerable_ones] = false
|
||||
options[:file] = options[:file] || DATA_DIR + '/timthumbs.txt'
|
||||
options[:vulns_file] = 'xxx'
|
||||
options[:vulns_xpath] = 'xxx'
|
||||
options[:vulns_xpath_2] = 'xxx'
|
||||
|
||||
WpOptions.check_options(options)
|
||||
if theme_name == nil
|
||||
custom_items = nil
|
||||
else
|
||||
custom_items = targets_url_from_theme(theme_name, options)
|
||||
end
|
||||
@wp_timthumbs = WpEnumerator.enumerate(options, custom_items)
|
||||
end
|
||||
@wp_timthumbs
|
||||
end
|
||||
|
||||
protected
|
||||
def targets_url_from_theme(theme_name, options)
|
||||
targets = []
|
||||
theme_name = URI.escape(theme_name)
|
||||
|
||||
%w{
|
||||
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
|
||||
}.each do |file|
|
||||
targets << WpItem.new(
|
||||
base_url: options[:base_url],
|
||||
path: "themes/#{theme_name}/#{file}",
|
||||
wp_content_dir: options[:wp_content_dir],
|
||||
name: theme_name,
|
||||
vulns_file: 'XX',
|
||||
type: 'timthumbs',
|
||||
wp_plugins_dir: options[:wp_plugins_dir]
|
||||
)
|
||||
end
|
||||
targets
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,117 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
module WpUsernames
|
||||
|
||||
# Enumerate wordpress usernames by using Veronica Valeros's technique:
|
||||
# http://seclists.org/fulldisclosure/2011/May/493
|
||||
#
|
||||
# Available options :
|
||||
# :range - default : 1..10
|
||||
#
|
||||
# returns an array of WpUser (can be empty)
|
||||
def usernames(options = {})
|
||||
range = options[:range] || (1..10)
|
||||
browser = Browser.instance
|
||||
usernames = []
|
||||
|
||||
range.each do |author_id|
|
||||
url = author_url(author_id)
|
||||
response = browser.get(url)
|
||||
|
||||
username = nil
|
||||
nickname = nil
|
||||
if response.code == 301 # username in location?
|
||||
username = response.headers_hash['location'][%r{/author/([^/\b]+)/?}i, 1]
|
||||
# Get the real name from the redirect site
|
||||
nickname = get_nickname_from_url(url)
|
||||
elsif response.code == 200 # username in body?
|
||||
# get the username from the author feed URL
|
||||
username = get_username_from_response(response)
|
||||
nickname = get_nickname_from_response(response)
|
||||
end
|
||||
|
||||
unless username == nil and nickname == nil
|
||||
usernames << WpUser.new(username, author_id, nickname)
|
||||
end
|
||||
end
|
||||
usernames = remove_junk_from_nickname(usernames)
|
||||
|
||||
# clean the array, remove nils and possible duplicates
|
||||
usernames.flatten!
|
||||
usernames.compact!
|
||||
usernames.uniq
|
||||
end
|
||||
|
||||
def get_nickname_from_url(url)
|
||||
resp = Browser.instance.get_and_follow_location(url)
|
||||
nickname = nil
|
||||
if resp.code == 200
|
||||
nickname = extract_nickname_from_body(resp.body)
|
||||
end
|
||||
nickname
|
||||
end
|
||||
|
||||
def get_nickname_from_response(resp)
|
||||
nickname = nil
|
||||
if resp.code == 200
|
||||
nickname = extract_nickname_from_body(resp.body)
|
||||
end
|
||||
nickname
|
||||
end
|
||||
|
||||
def get_username_from_response(resp)
|
||||
# Feed URL with Permalinks
|
||||
username = resp.body[%r{/author/([^/\b]+)/?}i, 1]
|
||||
if username.nil?
|
||||
# No Permalinks
|
||||
username = resp.body[%r{<body class="archive author author-([^\s]+) author-(\d+)}i, 1]
|
||||
end
|
||||
username
|
||||
end
|
||||
|
||||
def extract_nickname_from_body(body)
|
||||
body[%r{<title>([^<]*)</title>}i, 1]
|
||||
end
|
||||
|
||||
def remove_junk_from_nickname(usernames)
|
||||
unless usernames.kind_of? Array
|
||||
raise('Need an array as input')
|
||||
end
|
||||
nicknames = []
|
||||
usernames.each do |u|
|
||||
unless u.kind_of? WpUser
|
||||
raise('Items must be of type WpUser')
|
||||
end
|
||||
nickname = u.nickname
|
||||
unless nickname == 'empty'
|
||||
nicknames << nickname
|
||||
end
|
||||
end
|
||||
junk = get_equal_string_end(nicknames)
|
||||
usernames.each do |u|
|
||||
u.nickname = u.nickname.sub(/#{Regexp.escape(junk)}$/, '')
|
||||
end
|
||||
usernames
|
||||
end
|
||||
|
||||
def author_url(author_id)
|
||||
@uri.merge("?author=#{author_id}").to_s
|
||||
end
|
||||
end
|
||||
@@ -1,40 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
class Vulnerable
|
||||
|
||||
attr_reader :vulns_file, :vulns_xpath
|
||||
|
||||
# @return an array of WpVulnerability (can be empty)
|
||||
def vulnerabilities
|
||||
xml = xml(@vulns_file)
|
||||
vulnerabilities = []
|
||||
|
||||
xml.xpath(@vulns_xpath).each do |node|
|
||||
vulnerabilities << WpVulnerability.new(
|
||||
node.search('title').text,
|
||||
node.search('reference').map(&:text),
|
||||
node.search('type').text,
|
||||
node.search('metasploit').map(&:text)
|
||||
)
|
||||
end
|
||||
vulnerabilities
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,78 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
class WpDetector
|
||||
|
||||
def self.aggressive_detection(options, items = [])
|
||||
WpOptions.check_options(options)
|
||||
|
||||
result = items
|
||||
if items == nil or items.length == 0
|
||||
unless options[:only_vulnerable_ones]
|
||||
result = passive_detection(options[:base_url], options[:type], options[:wp_content_dir])
|
||||
end
|
||||
end
|
||||
|
||||
enum_results = WpEnumerator.enumerate(options)
|
||||
enum_results.each do |enum_result|
|
||||
already_present = false
|
||||
result.each do |r|
|
||||
# Already found via passive detection
|
||||
if r.name == enum_result.name
|
||||
already_present = true
|
||||
break
|
||||
end
|
||||
end
|
||||
unless already_present
|
||||
result << enum_result
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# plugins and themes can be found in the source code :
|
||||
# <script src='http://example.com/wp-content/plugins/s2member/...' />
|
||||
# <link rel='stylesheet' href='http://example.com/wp-content/plugins/wp-minify/..' type='text/css' media='screen'/>
|
||||
# ...
|
||||
def self.passive_detection(url, type, wp_content_dir)
|
||||
items = []
|
||||
response = Browser.instance.get(url)
|
||||
regex1 = %r{(?:[^=:]+)\s?(?:=|:)\s?(?:"|')[^"']+\\?/}
|
||||
regex2 = %r{\\?/}
|
||||
regex3 = %r{\\?/([^/\\"']+)\\?(?:/|"|')}
|
||||
# Custom wp-content dir is now used in this regex
|
||||
names = response.body.scan(/#{regex1}#{Regexp.escape(wp_content_dir)}#{regex2}#{Regexp.escape(type)}#{regex3}/i)
|
||||
|
||||
names.flatten!
|
||||
names.uniq!
|
||||
|
||||
names.each do |item|
|
||||
items << WpItem.new(
|
||||
base_url: url,
|
||||
name: item,
|
||||
type: type,
|
||||
path: "#{item}/",
|
||||
wp_content_dir: wp_content_dir,
|
||||
vulns_file: ''
|
||||
)
|
||||
end
|
||||
items
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,146 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
# Enumerate over a given set of items and check if they exist
|
||||
class WpEnumerator
|
||||
|
||||
# Enumerate the given Targets
|
||||
#
|
||||
# ==== Attributes
|
||||
#
|
||||
# * +targets+ - targets to enumerate
|
||||
# * * +:base_url+ - Base URL
|
||||
# * * +:wp_content+ - wp-content directory
|
||||
# * * +:path+ - Path to plugin
|
||||
# * +type+ - "plugins" or "themes", item to enumerate
|
||||
# * +filename+ - filename in the data directory with paths
|
||||
# * +show_progression+ - Show a progress bar during enumeration
|
||||
def self.enumerate(options = {}, items = nil)
|
||||
|
||||
WpOptions.check_options(options)
|
||||
|
||||
targets = self.generate_items(options)
|
||||
|
||||
unless items == nil
|
||||
items.each do |i|
|
||||
targets << i
|
||||
end
|
||||
end
|
||||
|
||||
found = []
|
||||
queue_count = 0
|
||||
request_count = 0
|
||||
enum_browser = Browser.instance
|
||||
enum_hydra = enum_browser.hydra
|
||||
enumerate_size = targets.size
|
||||
exclude_regexp = options[:exclude_content_based] ? %r{#{options[:exclude_content_based]}} : nil
|
||||
show_progression = options[:show_progression] || false
|
||||
|
||||
targets.each do |target|
|
||||
url = target.get_full_url
|
||||
|
||||
request = enum_browser.forge_request(url, cache_ttl: 0, followlocation: true)
|
||||
request_count += 1
|
||||
|
||||
request.on_complete do |response|
|
||||
page_hash = Digest::MD5.hexdigest(response.body)
|
||||
|
||||
print "\rChecking for #{enumerate_size} total #{options[:type]}... #{(request_count * 100) / enumerate_size}% complete." if show_progression
|
||||
|
||||
if WpTarget.valid_response_codes.include?(response.code)
|
||||
if page_hash != options[:error_404_hash] and page_hash != options[:homepage_hash]
|
||||
if options[:exclude_content_based]
|
||||
unless response.body[exclude_regexp]
|
||||
found << target
|
||||
end
|
||||
else
|
||||
found << target
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
enum_hydra.queue(request)
|
||||
queue_count += 1
|
||||
|
||||
if queue_count == enum_browser.max_threads
|
||||
enum_hydra.run
|
||||
queue_count = 0
|
||||
end
|
||||
end
|
||||
|
||||
enum_hydra.run
|
||||
found
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.generate_items(options = {})
|
||||
only_vulnerable = options[:only_vulnerable_ones]
|
||||
file = options[:file]
|
||||
vulns_file = options[:vulns_file]
|
||||
wp_content_dir = options[:wp_content_dir]
|
||||
url = options[:base_url]
|
||||
type = options[:type]
|
||||
plugins_dir = options[:wp_plugins_dir]
|
||||
targets_url = []
|
||||
|
||||
unless only_vulnerable
|
||||
# Open and parse the 'most popular' plugin list...
|
||||
File.open(file, 'r') do |f|
|
||||
f.readlines.collect do |line|
|
||||
l = line.strip
|
||||
targets_url << WpItem.new(
|
||||
base_url: url,
|
||||
path: l,
|
||||
wp_content_dir: wp_content_dir,
|
||||
name: l =~ /.+\/.+/ ? File.dirname(l) : l.sub(/\/$/, ''),
|
||||
vulns_file: vulns_file,
|
||||
type: type,
|
||||
wp_plugins_dir: plugins_dir
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Timthumbs have no XML file
|
||||
unless type =~ /timthumbs/i
|
||||
xml = xml(vulns_file)
|
||||
|
||||
# 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|
|
||||
name = node.attribute('name').text
|
||||
targets_url << WpItem.new(
|
||||
base_url: url,
|
||||
path: name,
|
||||
wp_content_dir: wp_content_dir,
|
||||
name: name,
|
||||
vulns_file: vulns_file,
|
||||
type: type,
|
||||
wp_plugins_dir: plugins_dir
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
targets_url.flatten! { |t| t.name }
|
||||
targets_url.uniq! { |t| t.name }
|
||||
# randomize the plugins array to *maybe* help in some crappy IDS/IPS/WAF detection
|
||||
targets_url.sort_by! { rand }
|
||||
end
|
||||
end
|
||||
@@ -1,196 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require "wpscan/vulnerable"
|
||||
|
||||
class WpItem < Vulnerable
|
||||
attr_reader :base_url, :path, :wp_content_dir, :name, :vulns_file, :vulns_xpath, :wp_plugins_dir, :type
|
||||
@version = nil
|
||||
|
||||
def initialize(options)
|
||||
@type = options[:type]
|
||||
@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"
|
||||
@base_url = options[:base_url]
|
||||
@path = options[:path]
|
||||
@name = options[:name] || extract_name_from_url
|
||||
@vulns_file = options[:vulns_file]
|
||||
@vulns_xpath = options[:vulns_xpath].sub(/\$name\$/, @name) unless options[:vulns_xpath] == nil
|
||||
|
||||
raise('base_url not set') unless @base_url
|
||||
raise('path not set') unless @path
|
||||
raise('wp_content_dir not set') unless @wp_content_dir
|
||||
raise('name not set') unless @name
|
||||
raise('vulns_file not set') unless @vulns_file
|
||||
raise('type not set') unless @type
|
||||
end
|
||||
|
||||
# The wordpress.org plugins directory URL
|
||||
# See: https://github.com/wpscanteam/wpscan/issues/100
|
||||
def wp_org_url
|
||||
case @type
|
||||
when 'themes'
|
||||
return URI('http://wordpress.org/extend/themes/').merge("#@name/")
|
||||
when 'plugins'
|
||||
return URI('http://wordpress.org/extend/plugins/').merge("#@name/")
|
||||
else
|
||||
raise("No Wordpress URL for #@type")
|
||||
end
|
||||
end
|
||||
|
||||
# returns true if this theme or plugin is hosted on wordpress.org
|
||||
def wp_org_item?
|
||||
case @type
|
||||
when 'themes'
|
||||
file = THEMES_FULL_FILE
|
||||
when 'plugins'
|
||||
file = PLUGINS_FULL_FILE
|
||||
else
|
||||
raise("Unknown type #@type")
|
||||
end
|
||||
f = File.readlines(file, encoding: 'UTF-8').grep(/^#{Regexp.escape(@name)}$/i)
|
||||
f.empty? ? false : true
|
||||
end
|
||||
|
||||
def get_sub_folder
|
||||
case @type
|
||||
when 'themes'
|
||||
folder = 'themes'
|
||||
when 'timthumbs'
|
||||
# not needed
|
||||
folder = nil
|
||||
else
|
||||
raise("unknown type #@type")
|
||||
end
|
||||
folder
|
||||
end
|
||||
|
||||
# Get the full url for this item
|
||||
def get_full_url
|
||||
url = @base_url.to_s.end_with?('/') ? @base_url.to_s : "#@base_url/"
|
||||
# remove first and last /
|
||||
wp_content_dir = @wp_content_dir.sub(/^\//, "").sub(/\/$/, '')
|
||||
# remove first /
|
||||
path = @path.sub(/^\//, '')
|
||||
if type == 'plugins'
|
||||
# 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}"))
|
||||
elsif type == 'timthumbs'
|
||||
# timthumbs have folder in path variable
|
||||
ret = URI.parse(URI.encode("#{url}#{wp_content_dir}/#{path}"))
|
||||
else
|
||||
ret = URI.parse(URI.encode("#{url}#{wp_content_dir}/#{get_sub_folder}/#{path}"))
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# Gets the full url for this item without filenames
|
||||
def get_url_without_filename
|
||||
location_url = get_full_url.to_s
|
||||
valid_location_url = location_url[%r{^(https?://.*/)[^.]+\.[^/]+$}, 1]
|
||||
unless valid_location_url
|
||||
valid_location_url = add_trailing_slash(location_url)
|
||||
end
|
||||
URI.parse(URI.encode(valid_location_url))
|
||||
end
|
||||
|
||||
# Returns version number from readme.txt if it exists
|
||||
def version
|
||||
unless @version
|
||||
response = Browser.instance.get(readme_url.to_s)
|
||||
@version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1]
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
# Is directory listing enabled?
|
||||
def directory_listing?
|
||||
# Need to remove to file part from the url
|
||||
Browser.instance.get(get_url_without_filename).body[%r{<title>Index of}] ? true : false
|
||||
end
|
||||
|
||||
# Extract item name from a url
|
||||
def extract_name_from_url
|
||||
get_full_url.to_s[%r{^(https?://.*/([^/]+)/)}i, 2]
|
||||
end
|
||||
|
||||
# To string. Adds a version number if detected
|
||||
def to_s
|
||||
item_version = version
|
||||
"#@name#{' v' + item_version.strip if item_version}"
|
||||
end
|
||||
|
||||
# Compare
|
||||
def ==(other)
|
||||
other.name == self.name
|
||||
end
|
||||
|
||||
# Compare
|
||||
def ===(other)
|
||||
other.name == self.name
|
||||
end
|
||||
|
||||
# Compare
|
||||
def <=>(other)
|
||||
other.name <=> self.name
|
||||
end
|
||||
|
||||
# Url for readme.txt
|
||||
def readme_url
|
||||
get_url_without_filename.merge('readme.txt')
|
||||
end
|
||||
|
||||
# Url for changelog.txt
|
||||
def changelog_url
|
||||
get_url_without_filename.merge('changelog.txt')
|
||||
end
|
||||
|
||||
def error_log_url
|
||||
get_url_without_filename.merge('error_log')
|
||||
end
|
||||
|
||||
# Discover any error_log files created by WordPress
|
||||
# These are created by the WordPress error_log() function
|
||||
# They are normally found in the /plugins/ directory,
|
||||
# however can also be found in their specific plugin dir.
|
||||
# http://www.exploit-db.com/ghdb/3714/
|
||||
def error_log?
|
||||
response_body = Browser.instance.get(error_log_url, headers: {'range' => 'bytes=0-700'}).body
|
||||
response_body[%r{PHP Fatal error}i] ? true : false
|
||||
end
|
||||
|
||||
# readme.txt present?
|
||||
def has_readme?
|
||||
unless @readme
|
||||
status = Browser.instance.get(readme_url).code
|
||||
@readme = status == 200 ? true : false
|
||||
end
|
||||
@readme
|
||||
end
|
||||
|
||||
# changelog.txt present?
|
||||
def has_changelog?
|
||||
unless @changelog
|
||||
status = Browser.instance.get(changelog_url).code
|
||||
@changelog = status == 200 ? true : false
|
||||
end
|
||||
@changelog
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,52 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
# Options Hash
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * +url+ - The base URL of the WordPress site
|
||||
# * +only_vulnerable_ones+ - Only detect vulnerable items
|
||||
# * +file+ - Filename with items to detect
|
||||
# * +vulns_file+ - XML file with vulnerabilities
|
||||
# * +vulns_xpath+ - XPath for vulnerability XML file
|
||||
# * +vulns_xpath_2+ - XPath for vulnerability XML file
|
||||
# * +wp_content_dir+ - Name of the wp-content directory
|
||||
# * +show_progression+ - Show a progress bar during enumeration
|
||||
# * +error_404_hash+ - MD5 hash of a 404 page
|
||||
# * +type+ - Type: plugins, themes
|
||||
class WpOptions
|
||||
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('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('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_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('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('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
|
||||
raise("Unknown type #{options[:type]}")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,32 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
class WpPlugin < WpItem
|
||||
def initialize(options = {})
|
||||
if options[:vulns_file].nil? or options[:vulns_file] == ''
|
||||
options[:vulns_file] = PLUGINS_VULNS_FILE
|
||||
end
|
||||
|
||||
options[:vulns_xpath] = "//plugin[@name='$name$']/vulnerability"
|
||||
options[:vulns_xpath_2] = '//plugin'
|
||||
options[:type] = 'plugins'
|
||||
|
||||
super(options)
|
||||
end
|
||||
end
|
||||
@@ -23,10 +23,6 @@ class WpTarget < WebSite
|
||||
include WpConfigBackup
|
||||
include WpLoginProtection
|
||||
include Malwares
|
||||
include WpUsernames
|
||||
include WpTimthumbs
|
||||
include WpPlugins
|
||||
include WpThemes
|
||||
include BruteForce
|
||||
|
||||
attr_reader :verbose
|
||||
@@ -92,7 +88,17 @@ class WpTarget < WebSite
|
||||
|
||||
# return WpVersion
|
||||
def version
|
||||
WpVersion.find(@uri, wp_content_dir)
|
||||
WpVersion.find(@uri, wp_content_dir, wp_plugins_dir)
|
||||
end
|
||||
|
||||
def has_plugin?(name, version = nil)
|
||||
WpPlugin.new(
|
||||
@uri,
|
||||
name: name,
|
||||
version: version,
|
||||
wp_content_dir: wp_content_dir,
|
||||
wp_plugins_dir: wp_plugins_dir
|
||||
).exists?
|
||||
end
|
||||
|
||||
def wp_content_dir
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require "wpscan/vulnerable"
|
||||
|
||||
class WpTheme < WpItem
|
||||
|
||||
attr_reader :style_url, :version
|
||||
|
||||
def initialize(options = {})
|
||||
if options[:vulns_file].nil? or options[:vulns_file] == ''
|
||||
options[:vulns_file] = THEMES_VULNS_FILE
|
||||
end
|
||||
|
||||
options[:vulns_xpath] = "//theme[@name='$name$']/vulnerability"
|
||||
options[:type] = 'themes'
|
||||
@version = options[:version]
|
||||
@style_url = options[:style_url]
|
||||
|
||||
super(options)
|
||||
end
|
||||
|
||||
def version
|
||||
unless @version
|
||||
if @style_url
|
||||
url = @style_url
|
||||
else
|
||||
url = default_style_url
|
||||
end
|
||||
@version = Browser.instance.get(url).body[%r{Version:\s([^\s]+)}i, 1]
|
||||
|
||||
# Get Version from readme.txt
|
||||
if @version.nil?
|
||||
@version = super
|
||||
end
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
def default_style_url
|
||||
get_url_without_filename.merge('style.css')
|
||||
end
|
||||
|
||||
def self.find(target_uri)
|
||||
self.methods.grep(/find_from_/).each do |method_to_call|
|
||||
theme = self.send(method_to_call, target_uri)
|
||||
|
||||
return theme if theme
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def ===(wp_theme)
|
||||
wp_theme.name === @name and wp_theme.version === @version
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Discover the wordpress theme name by parsing the css link rel
|
||||
def self.find_from_css_link(target_uri)
|
||||
response = Browser.instance.get_and_follow_location(target_uri.to_s)
|
||||
|
||||
# https + domain is optional because of relative links
|
||||
matches = %r{(?:https?://[^"']+)?/([^/]+)/themes/([^"']+)/style.css}i.match(response.body)
|
||||
if matches
|
||||
style_url = matches[0]
|
||||
wp_content_dir = matches[1]
|
||||
theme_name = matches[2]
|
||||
|
||||
return new(
|
||||
name: theme_name,
|
||||
style_url: style_url,
|
||||
base_url: target_uri,
|
||||
path: theme_name,
|
||||
wp_content_dir: wp_content_dir
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# http://code.google.com/p/wpscan/issues/detail?id=141
|
||||
def self.find_from_wooframework(target_uri)
|
||||
body = Browser.instance.get(target_uri.to_s).body
|
||||
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
|
||||
|
||||
matches = regexp.match(body)
|
||||
if matches
|
||||
woo_theme_name = matches[1]
|
||||
woo_theme_version = matches[2]
|
||||
woo_framework_version = matches[3] # Not used at this time
|
||||
|
||||
return new(
|
||||
name: woo_theme_name,
|
||||
version: woo_theme_version,
|
||||
base_url: target_uri.to_s,
|
||||
path: woo_theme_name
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,76 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
class WpUser
|
||||
|
||||
def name
|
||||
if @name.nil? or @name.to_s.strip.empty?
|
||||
return 'empty'
|
||||
end
|
||||
@name
|
||||
end
|
||||
|
||||
def name=(new_name)
|
||||
@name = new_name
|
||||
end
|
||||
|
||||
def id
|
||||
if @id.nil? or @id.to_s.strip.empty?
|
||||
return 'empty'
|
||||
end
|
||||
@id
|
||||
end
|
||||
|
||||
def id=(new_id)
|
||||
@id = new_id
|
||||
end
|
||||
|
||||
def nickname
|
||||
if @nickname.nil? or @nickname.to_s.strip.empty?
|
||||
return 'empty'
|
||||
end
|
||||
@nickname
|
||||
end
|
||||
|
||||
def nickname=(new_nickname)
|
||||
@nickname = new_nickname
|
||||
end
|
||||
|
||||
def initialize(name, id, nickname)
|
||||
self.name = name
|
||||
self.id = id
|
||||
self.nickname = nickname
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
other.name <=> self.name
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self === other
|
||||
end
|
||||
|
||||
def ===(other)
|
||||
other.name === self.name and other.id === self.id and other.nickname === self.nickname
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
self === other
|
||||
end
|
||||
end
|
||||
@@ -1,202 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require "wpscan/vulnerable"
|
||||
|
||||
class WpVersion < Vulnerable
|
||||
|
||||
attr_reader :number, :discovery_method
|
||||
|
||||
def initialize(number, options = {})
|
||||
@number = number
|
||||
@discovery_method = options[:discovery_method]
|
||||
@vulns_file = options[:vulns_file] || WP_VULNS_FILE
|
||||
@vulns_xpath = "//wordpress[@version='#{@number}']/vulnerability"
|
||||
end
|
||||
|
||||
# Will use all method self.find_from_* to try to detect the version
|
||||
# Once the version is found, it will return a WpVersion object
|
||||
# The method_name will be without 'find_from_' and '_' will be replace by ' ' (IE 'meta generator', 'rss generator' etc)
|
||||
# If the version is not found, nil is returned
|
||||
#
|
||||
# The order in which the find_from_* methods are is important, they will be called in the same order
|
||||
# (find_from_meta_generator, find_from_rss_generator etc)
|
||||
def self.find(target_uri, wp_content_dir)
|
||||
options = {
|
||||
base_uri: target_uri,
|
||||
wp_content_dir: wp_content_dir
|
||||
}
|
||||
self.methods.grep(/find_from_/).each do |method_to_call|
|
||||
version = self.send(method_to_call, options)
|
||||
|
||||
if version
|
||||
return new(version, discovery_method: method_to_call[%r{find_from_(.*)}, 1].gsub('_', ' '))
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Returns the first match of <pattern> in the body of the url
|
||||
def self.scan_url(base_uri, pattern, path = nil)
|
||||
url = path ? base_uri.merge(path).to_s : base_uri.to_s
|
||||
response = Browser.instance.get_and_follow_location(url)
|
||||
|
||||
response.body[pattern, 1]
|
||||
end
|
||||
|
||||
#
|
||||
# DO NOT Change the order of the following methods
|
||||
# unless you know what you are doing
|
||||
# See WpVersion.find
|
||||
#
|
||||
|
||||
# Attempts to find the wordpress version from,
|
||||
# the generator meta tag in the html source.
|
||||
#
|
||||
# The meta tag can be removed however it seems,
|
||||
# that it is reinstated on upgrade.
|
||||
def self.find_from_meta_generator(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{name="generator" content="wordpress #{WpVersion.version_pattern}"}i
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS feed source.
|
||||
def self.find_from_rss_generator(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{<generator>http://wordpress.org/\?v=#{WpVersion.version_pattern}</generator>}i,
|
||||
'feed/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find WordPress version from,
|
||||
# the generator tag in the RDF feed source.
|
||||
def self.find_from_rdf_generator(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{WpVersion.version_pattern}" />}i,
|
||||
'feed/rdf/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS2 feed source.
|
||||
#
|
||||
# Have not been able to find an example of this - Ryan
|
||||
#def self.find_from_rss2_generator(options)
|
||||
# WpVersion.scan_url(
|
||||
# options[:base_uri],
|
||||
# %r{<generator>http://wordpress.org/?v=(#{WpVersion.version_pattern})</generator>}i,
|
||||
# 'feed/rss/'
|
||||
# )
|
||||
#end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the Atom source.
|
||||
def self.find_from_atom_generator(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{<generator uri="http://wordpress.org/" version="#{WpVersion.version_pattern}">WordPress</generator>}i,
|
||||
'feed/atom/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the comment rss source.
|
||||
#
|
||||
# Have not been able to find an example of this - Ryan
|
||||
#def self.find_from_comments_rss_generator(options)
|
||||
# WpVersion.scan_url(
|
||||
# options[:base_uri],
|
||||
# %r{<!-- generator="WordPress/#{WpVersion.version_pattern}" -->}i,
|
||||
# 'comments/feed/'
|
||||
# )
|
||||
#end
|
||||
|
||||
# Uses data/wp_versions.xml to try to identify a
|
||||
# wordpress version.
|
||||
#
|
||||
# It does this by using client side file hashing
|
||||
#
|
||||
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
|
||||
#
|
||||
def self.find_from_advanced_fingerprinting(options)
|
||||
target_uri = options[:base_uri]
|
||||
version_xml = options[:version_xml] || WP_VERSIONS_FILE # needed for rpsec
|
||||
wp_content = options[:wp_content_dir]
|
||||
wp_plugins = "#{wp_content}/plugins"
|
||||
xml = xml(version_xml)
|
||||
|
||||
xml.xpath('//file').each do |node|
|
||||
file_src = node.attribute('src').text
|
||||
file_url = target_uri.merge(file_src).to_s.
|
||||
gsub(/\$wp-plugins\$/i, wp_plugins).
|
||||
gsub(/\$wp-content\$/i, wp_content)
|
||||
|
||||
response = Browser.instance.get(file_url)
|
||||
md5sum = Digest::MD5.hexdigest(response.body)
|
||||
|
||||
node.search('hash').each do |hash|
|
||||
if hash.attribute('md5').text == md5sum
|
||||
return hash.search('version').text
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the readme.html file.
|
||||
def self.find_from_readme(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{<br />\sversion #{WpVersion.version_pattern}}i,
|
||||
'readme.html'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the sitemap.xml file.
|
||||
#
|
||||
# See: http://code.google.com/p/wpscan/issues/detail?id=109
|
||||
def self.find_from_sitemap_generator(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{generator="wordpress/#{WpVersion.version_pattern}"}i,
|
||||
'sitemap.xml'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the p-links-opml.php file.
|
||||
def self.find_from_links_opml(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{generator="wordpress/#{WpVersion.version_pattern}"}i,
|
||||
'wp-links-opml.php'
|
||||
)
|
||||
end
|
||||
|
||||
# Used to check if the version is correct: must contain at least one dot.
|
||||
def self.version_pattern
|
||||
'([^\r\n"\']+\.[^\r\n"\']+)'
|
||||
end
|
||||
end
|
||||
@@ -1,29 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
class WpVulnerability
|
||||
attr_accessor :title, :references, :type, :metasploit_modules
|
||||
|
||||
def initialize(title, references, type, metasploit_modules)
|
||||
@title = title
|
||||
@references = references
|
||||
@type = type
|
||||
@metasploit_modules = metasploit_modules
|
||||
end
|
||||
end
|
||||
@@ -17,9 +17,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require 'wpscan/wp_enumerator'
|
||||
require 'wpscan/wp_item'
|
||||
|
||||
class StatsPlugin < Plugin
|
||||
|
||||
def initialize
|
||||
@@ -60,23 +57,16 @@ class StatsPlugin < Plugin
|
||||
xml(file).xpath("count(//vulnerability)").to_i
|
||||
end
|
||||
|
||||
def total_plugins(file=PLUGINS_FULL_FILE, xml=PLUGINS_VULNS_FILE)
|
||||
total('plugins', file, xml)
|
||||
def total_plugins(file=PLUGINS_FULL_FILE)
|
||||
lines_in_file(file)
|
||||
end
|
||||
|
||||
def total_themes(file=THEMES_FULL_FILE, xml=THEMES_VULNS_FILE)
|
||||
total('themes', file, xml)
|
||||
def total_themes(file=THEMES_FULL_FILE)
|
||||
lines_in_file(file)
|
||||
end
|
||||
|
||||
def total(type, file, xml)
|
||||
options = {
|
||||
type: type,
|
||||
file: file,
|
||||
vulns_file: xml,
|
||||
base_url: 'http://localhost',
|
||||
only_vulnerable_ones: false
|
||||
}
|
||||
WpEnumerator.generate_items(options).count
|
||||
def lines_in_file(file)
|
||||
IO.readlines(file).size
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
170
main.rb
170
main.rb
@@ -19,35 +19,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
def output_vulnerabilities(vulns)
|
||||
vulns.each do |vulnerability|
|
||||
puts
|
||||
puts ' | ' + red("* Title: #{vulnerability.title}")
|
||||
vulnerability.references.each do |r|
|
||||
puts ' | ' + red("* Reference: #{r}")
|
||||
end
|
||||
vulnerability.metasploit_modules.each do |m|
|
||||
puts ' | ' + red("* Metasploit module: #{get_metasploit_url(m)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def output_item_details(item)
|
||||
puts
|
||||
puts " | Name: #{item}" #this will also output the version number if detected
|
||||
puts " | Location: #{item.get_url_without_filename}"
|
||||
puts " | WordPress: #{item.wp_org_url}" if item.wp_org_item?
|
||||
puts ' | Directory listing enabled: Yes' if item.directory_listing?
|
||||
puts " | Readme: #{item.readme_url}" if item.has_readme?
|
||||
puts " | Changelog: #{item.changelog_url}" if item.has_changelog?
|
||||
|
||||
output_vulnerabilities(item.vulnerabilities)
|
||||
|
||||
if item.error_log?
|
||||
puts ' | ' + red('[!]') + " A WordPress error_log file has been found : #{item.error_log_url}"
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
@@ -193,38 +164,31 @@ def main
|
||||
puts
|
||||
end
|
||||
|
||||
wp_version = wp_target.version
|
||||
if wp_version
|
||||
puts green('[+]') + " WordPress version #{wp_version.number} identified from #{wp_version.discovery_method}"
|
||||
enum_options = {
|
||||
show_progression: true,
|
||||
exclude_content: wpscan_options.exclude_content_based
|
||||
}
|
||||
|
||||
version_vulnerabilities = wp_version.vulnerabilities
|
||||
|
||||
unless version_vulnerabilities.empty?
|
||||
puts
|
||||
puts red('[!]') + " We have identified #{version_vulnerabilities.size} vulnerabilities from the version number :"
|
||||
output_vulnerabilities(version_vulnerabilities)
|
||||
end
|
||||
if wp_version = wp_target.version
|
||||
wp_version.output
|
||||
end
|
||||
|
||||
wp_theme = wp_target.theme
|
||||
if wp_theme
|
||||
if wp_theme = wp_target.theme
|
||||
puts
|
||||
# Theme version is handled in wp_item.to_s
|
||||
# Theme version is handled in #to_s
|
||||
puts green('[+]') + " The WordPress theme in use is #{wp_theme}"
|
||||
output_item_details(wp_theme)
|
||||
wp_theme.output
|
||||
end
|
||||
|
||||
if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil
|
||||
puts
|
||||
puts green('[+]') + ' Enumerating plugins from passive detection ... '
|
||||
|
||||
plugins = wp_target.plugins_from_passive_detection(base_url: wp_target.uri, wp_content_dir: wp_target.wp_content_dir)
|
||||
if !plugins.empty?
|
||||
puts "#{plugins.size} plugins found :"
|
||||
wp_plugins = WpPlugins.passive_detection(wp_target)
|
||||
if !wp_plugins.empty?
|
||||
puts "#{wp_plugins.size} plugins found :"
|
||||
|
||||
plugins.each do |plugin|
|
||||
output_item_details(plugin)
|
||||
end
|
||||
wp_plugins.output
|
||||
else
|
||||
puts 'No plugins found :('
|
||||
end
|
||||
@@ -236,27 +200,18 @@ def main
|
||||
puts green('[+]') + " Enumerating installed plugins #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_plugins} ..."
|
||||
puts
|
||||
|
||||
options = {
|
||||
base_url: wp_target.uri,
|
||||
only_vulnerable_ones: wpscan_options.enumerate_only_vulnerable_plugins || false,
|
||||
show_progression: true,
|
||||
wp_content_dir: wp_target.wp_content_dir,
|
||||
error_404_hash: wp_target.error_404_hash,
|
||||
homepage_hash: wp_target.homepage_hash,
|
||||
wp_plugins_dir: wp_target.wp_plugins_dir,
|
||||
full: wpscan_options.enumerate_all_plugins,
|
||||
exclude_content_based: wpscan_options.exclude_content_based
|
||||
}
|
||||
|
||||
plugins = wp_target.plugins_from_aggressive_detection(options)
|
||||
if !plugins.empty?
|
||||
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
|
||||
)
|
||||
)
|
||||
if !wp_plugins.empty?
|
||||
puts
|
||||
puts
|
||||
puts green('[+]') + " We found #{plugins.size.to_s} plugins:"
|
||||
puts green('[+]') + " We found #{wp_plugins.size} plugins:"
|
||||
|
||||
plugins.each do |plugin|
|
||||
output_item_details(plugin)
|
||||
end
|
||||
wp_plugins.output
|
||||
else
|
||||
puts
|
||||
puts 'No plugins found :('
|
||||
@@ -269,26 +224,19 @@ def main
|
||||
puts green('[+]') + " Enumerating installed themes #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_themes} ..."
|
||||
puts
|
||||
|
||||
options = {
|
||||
base_url: wp_target.uri,
|
||||
only_vulnerable_ones: wpscan_options.enumerate_only_vulnerable_themes || false,
|
||||
show_progression: true,
|
||||
wp_content_dir: wp_target.wp_content_dir,
|
||||
error_404_hash: wp_target.error_404_hash,
|
||||
homepage_hash: wp_target.homepage_hash,
|
||||
full: wpscan_options.enumerate_all_themes,
|
||||
exclude_content_based: wpscan_options.exclude_content_based
|
||||
}
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
themes = wp_target.themes_from_aggressive_detection(options)
|
||||
if !themes.empty?
|
||||
if !wp_themes.empty?
|
||||
puts
|
||||
puts
|
||||
puts green('[+]') + " We found #{themes.size.to_s} themes:"
|
||||
puts green('[+]') + " We found #{wp_themes.size} themes:"
|
||||
|
||||
themes.each do |theme|
|
||||
output_item_details(theme)
|
||||
end
|
||||
wp_themes.output
|
||||
else
|
||||
puts
|
||||
puts 'No themes found :('
|
||||
@@ -300,26 +248,19 @@ def main
|
||||
puts green('[+]') + ' Enumerating timthumb files ...'
|
||||
puts
|
||||
|
||||
options = {
|
||||
base_url: wp_target.uri,
|
||||
show_progression: true,
|
||||
wp_content_dir: wp_target.wp_content_dir,
|
||||
error_404_hash: wp_target.error_404_hash,
|
||||
homepage_hash: wp_target.homepage_hash,
|
||||
exclude_content_based: wpscan_options.exclude_content_based
|
||||
}
|
||||
|
||||
theme_name = wp_theme ? wp_theme.name : nil
|
||||
if wp_target.has_timthumbs?(theme_name, options)
|
||||
timthumbs = wp_target.timthumbs
|
||||
|
||||
wp_timthumbs = WpTimthumbs.aggressive_detection(wp_target,
|
||||
enum_options.merge(
|
||||
file: DATA_DIR + '/timthumbs.txt',
|
||||
theme_name: wp_theme ? wp_theme.name : nil
|
||||
)
|
||||
)
|
||||
if !wp_timthumbs.empty?
|
||||
puts
|
||||
puts green('[+]') + " We found #{timthumbs.size.to_s} timthumb file/s :"
|
||||
puts green('[+]') + " We found #{timthumbs.size} timthumb file/s :"
|
||||
puts
|
||||
|
||||
timthumbs.each do |t|
|
||||
puts ' | ' + red('[!]') + " #{t.get_full_url.to_s}"
|
||||
end
|
||||
wp_timthumbs.output
|
||||
|
||||
puts
|
||||
puts red(' * Reference: http://www.exploit-db.com/exploits/17602/')
|
||||
else
|
||||
@@ -333,9 +274,14 @@ def main
|
||||
puts
|
||||
puts green('[+]') + ' Enumerating usernames ...'
|
||||
|
||||
usernames = wp_target.usernames(range: wpscan_options.enumerate_usernames_range)
|
||||
wp_users = WpUsers.aggressive_detection(wp_target,
|
||||
enum_options.merge(
|
||||
range: wpscan_options.enumerate_usernames_range,
|
||||
show_progression: false
|
||||
)
|
||||
)
|
||||
|
||||
if usernames.empty?
|
||||
if wp_users.empty?
|
||||
puts
|
||||
puts 'We did not enumerate any usernames :('
|
||||
puts 'Try supplying your own username with the --username option'
|
||||
@@ -343,24 +289,14 @@ def main
|
||||
exit(1)
|
||||
else
|
||||
puts
|
||||
puts green('[+]') + " We found the following #{usernames.length.to_s} username/s :"
|
||||
puts
|
||||
puts green('[+]') + " We found the following #{wp_users.size} user/s :"
|
||||
|
||||
max_id_length = usernames.sort { |a, b| a.id.to_s.length <=> b.id.to_s.length }.last.id.to_s.length
|
||||
max_name_length = usernames.sort { |a, b| a.name.length <=> b.name.length }.last.name.length
|
||||
max_nickname_length = usernames.sort { |a, b| a.nickname.length <=> b.nickname.length }.last.nickname.length
|
||||
|
||||
space = 1
|
||||
usernames.each do |u|
|
||||
id_string = "id: #{u.id.to_s.ljust(max_id_length + space)}"
|
||||
name_string = "name: #{u.name.ljust(max_name_length + space)}"
|
||||
nickname_string = "nickname: #{u.nickname.ljust(max_nickname_length + space)}"
|
||||
puts " | #{id_string}| #{name_string}| #{nickname_string}"
|
||||
end
|
||||
wp_users.output(' ' * 4)
|
||||
end
|
||||
|
||||
else
|
||||
usernames = [WpUser.new(wpscan_options.username, -1, 'empty')]
|
||||
# FIXME : Change the .username to .login (and also the --username in the CLI)
|
||||
wp_users = WpUsers.new << WpUser.new(wp_target, login: wpscan_options.username)
|
||||
end
|
||||
|
||||
# Start the brute forcer
|
||||
@@ -381,7 +317,7 @@ def main
|
||||
puts
|
||||
puts green('[+]') + ' Starting the password brute forcer'
|
||||
puts
|
||||
wp_target.brute_force(usernames, wpscan_options.wordlist, {show_progression: true})
|
||||
wp_target.brute_force(wp_users, wpscan_options.wordlist, { show_progression: true })
|
||||
else
|
||||
puts
|
||||
puts 'Brute forcing aborted'
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '../../../lib/wpscan/wpscan_helper')
|
||||
|
||||
describe 'common_helper' do
|
||||
describe '#get_equal_string' do
|
||||
after :each do
|
||||
output = get_equal_string_end(@input)
|
||||
|
||||
output.should == @expected
|
||||
end
|
||||
|
||||
it 'sould return an empty string' do
|
||||
@input = ['']
|
||||
@expected = ''
|
||||
end
|
||||
|
||||
it 'sould return an empty string' do
|
||||
@input = []
|
||||
@expected = ''
|
||||
end
|
||||
|
||||
it 'sould return asdf' do
|
||||
@input = ['kjh asdf', 'oijr asdf']
|
||||
@expected = ' asdf'
|
||||
end
|
||||
|
||||
it 'sould return « BlogName' do
|
||||
@input = ['user1 « BlogName',
|
||||
'user2 « BlogName',
|
||||
'user3 « BlogName',
|
||||
'user4 « BlogName']
|
||||
@expected = ' « BlogName'
|
||||
end
|
||||
|
||||
it 'sould return an empty string' do
|
||||
@input = %w{user1 user2 user3 user4}
|
||||
@expected = ''
|
||||
end
|
||||
|
||||
it 'sould return an empty string' do
|
||||
@input = ['user1 « BlogName',
|
||||
'user2 « BlogName',
|
||||
'user3 « BlogName',
|
||||
'user4 « BlogNamea']
|
||||
@expected = ''
|
||||
end
|
||||
|
||||
it 'sould return an empty string' do
|
||||
@input = %w{ user1 }
|
||||
@expected = ''
|
||||
end
|
||||
|
||||
it 'sould return | test' do
|
||||
@input = ['admin | test', 'test | test']
|
||||
@expected = ' | test'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -59,7 +59,7 @@ shared_examples_for 'BruteForce' do
|
||||
)
|
||||
end
|
||||
|
||||
user = WpUser.new('admin', 1, nil)
|
||||
user = WpUser.new(@module.uri, login: 'admin')
|
||||
result = @module.brute_force([user], @wordlist)
|
||||
|
||||
result.length.should == 1
|
||||
@@ -69,7 +69,7 @@ shared_examples_for 'BruteForce' do
|
||||
it 'should cover the timeout branch and return an empty array' do
|
||||
stub_request(:post, @module.login_url).to_timeout
|
||||
|
||||
user = WpUser.new('admin', 1, nil)
|
||||
user = WpUser.new(@module.uri, login: 'admin')
|
||||
result = @module.brute_force([user], @wordlist)
|
||||
result.should == []
|
||||
end
|
||||
|
||||
@@ -22,6 +22,7 @@ shared_examples_for 'WpLoginProtection' do
|
||||
before :each do
|
||||
@module = WpScanModuleSpec.new('http://example.localhost')
|
||||
@module.extend(WpLoginProtection)
|
||||
@module.stub(:wp_plugins_dir).and_return('wp-content/plugins')
|
||||
|
||||
@fixtures_dir = SPEC_FIXTURES_WPSCAN_MODULES_DIR + '/wp_login_protection'
|
||||
end
|
||||
@@ -70,10 +71,10 @@ shared_examples_for 'WpLoginProtection' do
|
||||
expected = plugin_name_from_fixture === plugin_name_from_symbol ? true : false
|
||||
|
||||
it "#{symbol_to_call} with #{fixture} should return #{expected}" do
|
||||
@plugin_name = plugin_name_from_fixture
|
||||
@fixture = @fixtures_dir + '/' + fixture
|
||||
@plugin_name = plugin_name_from_fixture
|
||||
@fixture = @fixtures_dir + '/' + fixture
|
||||
@symbol_to_call = symbol_to_call
|
||||
@expected = expected
|
||||
@expected = expected
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -89,33 +90,25 @@ shared_examples_for 'WpLoginProtection' do
|
||||
stub_request(:get, @module.send(:limit_login_attempts_url).to_s).to_return(status: 404)
|
||||
stub_request(:get, @module.send(:bluetrait_event_viewer_url).to_s).to_return(status: 404)
|
||||
|
||||
@module.login_protection_plugin().should === @plugin_expected
|
||||
@module.login_protection_plugin().should == @plugin_expected
|
||||
@module.has_login_protection?.should === @has_protection_expected
|
||||
end
|
||||
|
||||
it 'should return nil if no protection is present' do
|
||||
@fixture = @fixtures_dir + '/wp-login-clean.php'
|
||||
@plugin_expected = nil
|
||||
@fixture = @fixtures_dir + '/wp-login-clean.php'
|
||||
@plugin_expected = nil
|
||||
@has_protection_expected = false
|
||||
end
|
||||
|
||||
it 'should return a login-lockdown WpPlugin object' do
|
||||
@fixture = @fixtures_dir + '/wp-login-login_lockdown.php'
|
||||
@plugin_expected = WpPlugin.new(
|
||||
base_url: @module.url,
|
||||
path: '/plugins/login-lockdown/',
|
||||
name: 'login-lockdown'
|
||||
)
|
||||
@fixture = @fixtures_dir + '/wp-login-login_lockdown.php'
|
||||
@plugin_expected = WpPlugin.new(@module.uri, name: 'login-lockdown')
|
||||
@has_protection_expected = true
|
||||
end
|
||||
|
||||
it 'should return a login-lock WpPlugin object' do
|
||||
@fixture = @fixtures_dir + '/wp-login-login_lock.php'
|
||||
@plugin_expected = WpPlugin.new(
|
||||
base_url: @module.url,
|
||||
path: '/plugins/login-lock/',
|
||||
name: 'login-lock'
|
||||
)
|
||||
@fixture = @fixtures_dir + '/wp-login-login_lock.php'
|
||||
@plugin_expected = WpPlugin.new(@module.uri, name: 'login-lock')
|
||||
@has_protection_expected = true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
shared_examples_for 'WpPlugins' do
|
||||
|
||||
before :all do
|
||||
@fixtures_dir = SPEC_FIXTURES_WPSCAN_MODULES_DIR + '/wp_plugins'
|
||||
@plugins_file = @fixtures_dir + '/plugins.txt'
|
||||
@plugin_vulns_file = @fixtures_dir + '/plugin_vulns.xml'
|
||||
|
||||
@wp_url = 'http://example.localhost/'
|
||||
end
|
||||
|
||||
before :each do
|
||||
@module = WpScanModuleSpec.new(@wp_url)
|
||||
@module.error_404_hash = Digest::MD5.hexdigest('Error 404!')
|
||||
@module.homepage_hash = Digest::MD5.hexdigest('Homepage!')
|
||||
@module.extend(WpPlugins)
|
||||
|
||||
@options = {
|
||||
base_url: @wp_url,
|
||||
only_vulnerable_ones: false,
|
||||
show_progression: false,
|
||||
error_404_hash: @module.error_404_hash,
|
||||
homepage_hash: @module.homepage_hash,
|
||||
vulns_file: @plugin_vulns_file,
|
||||
file: @plugins_file,
|
||||
type: 'plugins',
|
||||
wp_content_dir: 'wp-content',
|
||||
vulns_xpath_2: '//plugin'
|
||||
}
|
||||
File.exist?(@plugin_vulns_file).should == true
|
||||
File.exist?(@plugins_file).should == true
|
||||
|
||||
# These targets are listed in @fixtures_dir + '/plugins.txt'
|
||||
# TODO : load them directly from the fixture file
|
||||
@targets = [
|
||||
WpPlugin.new(
|
||||
{
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'exclude-pages/exclude_pages.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'exclude-pages'
|
||||
}),
|
||||
WpPlugin.new(
|
||||
{
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'display-widgets/display-widgets.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'display-widgets'
|
||||
}),
|
||||
WpPlugin.new(
|
||||
{
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'media-library',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'media-library'
|
||||
}),
|
||||
WpPlugin.new(
|
||||
{
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'deans',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'deans'
|
||||
}),
|
||||
WpPlugin.new(
|
||||
{
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'formidable/formidable.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'formidable'
|
||||
}),
|
||||
WpPlugin.new(
|
||||
{
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'regenerate-thumbnails/readme.txt',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'regenerate-thumbnails'
|
||||
})
|
||||
]
|
||||
end
|
||||
|
||||
describe '#plugins_from_passive_detection' do
|
||||
let(:passive_detection_fixtures) { @fixtures_dir + '/passive_detection' }
|
||||
|
||||
it 'should return an empty array' do
|
||||
stub_request_to_fixture(url: @module.url, fixture: File.new(passive_detection_fixtures + '/no_plugins.htm'))
|
||||
plugins = @module.plugins_from_passive_detection(base_url: @module.url, wp_content_dir: 'wp-content')
|
||||
plugins.should be_empty
|
||||
end
|
||||
|
||||
it 'should return the expected plugins' do
|
||||
stub_request_to_fixture(url: @module.url, fixture: File.new(passive_detection_fixtures + '/various_plugins.htm'))
|
||||
|
||||
expected_plugin_names = %w{
|
||||
wp-minify
|
||||
comment-info-tip
|
||||
tweet-blender
|
||||
optinpop
|
||||
s2member
|
||||
wp-polls
|
||||
commentluv
|
||||
}
|
||||
expected_plugins = []
|
||||
expected_plugin_names.each do |plugin_name|
|
||||
expected_plugins << WpPlugin.new(
|
||||
base_url: @module.url,
|
||||
path: "/plugins/#{plugin_name}/",
|
||||
name: plugin_name
|
||||
)
|
||||
end
|
||||
|
||||
plugins = @module.plugins_from_passive_detection(base_url: @module.url, wp_content_dir: 'wp-content')
|
||||
plugins.should_not be_empty
|
||||
plugins.length.should == expected_plugins.length
|
||||
plugins.sort.should == expected_plugins.sort
|
||||
end
|
||||
end
|
||||
|
||||
describe '#plugins_from_aggressive_detection' do
|
||||
|
||||
before :each do
|
||||
stub_request(:get, @module.uri.to_s).to_return(status: 200)
|
||||
# Point all targets to a 404
|
||||
@targets.each do |target|
|
||||
stub_request(:get, target.get_full_url.to_s).to_return(status: 404)
|
||||
# to_s calls readme_url
|
||||
stub_request(:get, target.readme_url.to_s).to_return(status: 404)
|
||||
end
|
||||
end
|
||||
|
||||
after :each do
|
||||
@passive_detection_fixture = SPEC_FIXTURES_DIR + '/empty-file' unless @passive_detection_fixture
|
||||
stub_request_to_fixture(url: "#{@module.uri}/".sub(/\/\/$/, '/'), fixture: @passive_detection_fixture)
|
||||
detected = @module.plugins_from_aggressive_detection(@options)
|
||||
detected.length.should == @expected_plugins.length
|
||||
detected.sort.should == @expected_plugins.sort
|
||||
end
|
||||
|
||||
it 'should return an empty array' do
|
||||
@expected_plugins = []
|
||||
end
|
||||
|
||||
it 'should return an array with 3 WpPlugin (1 detected from passive method)' do
|
||||
@passive_detection_fixture = @fixtures_dir + '/passive_detection/one_plugin.htm'
|
||||
@expected_plugins = @targets.sample(2)
|
||||
@expected_plugins.each do |p|
|
||||
stub_request(:get, p.get_full_url.to_s).to_return(status: 200)
|
||||
end
|
||||
new_plugin = WpPlugin.new(
|
||||
base_url: 'http://example.localhost/',
|
||||
path: '/plugins/comment-info-tip/',
|
||||
name: 'comment-info-tip'
|
||||
)
|
||||
stub_request(:get, new_plugin.readme_url.to_s).to_return(status: 200)
|
||||
@expected_plugins << new_plugin
|
||||
end
|
||||
|
||||
# testing response codes
|
||||
WpTarget.valid_response_codes.each do |valid_response_code|
|
||||
it "should detect the plugin if the reponse.code is #{valid_response_code}" do
|
||||
@expected_plugins = []
|
||||
plugin_url = [@targets.sample(1)[0]]
|
||||
plugin_url.should_not be_nil
|
||||
plugin_url.length.should == 1
|
||||
@expected_plugins = plugin_url
|
||||
stub_request(:get, plugin_url[0].get_full_url.to_s).to_return(status: valid_response_code)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should not detect the plugin if there is a redirection to the homepage' do
|
||||
# Let's pick up 2 plugins (The first one will redirect to the homepage)
|
||||
plugins = @targets.sample(2)
|
||||
stub_request(:get, plugins[0].get_full_url.to_s).to_return(status: 200, body: 'Homepage!')
|
||||
stub_request(:get, plugins[1].get_full_url.to_s).to_return(status: 200)
|
||||
|
||||
@expected_plugins = [plugins[1]]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,211 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
shared_examples_for 'WpThemes' do
|
||||
|
||||
before :all do
|
||||
@fixtures_dir = SPEC_FIXTURES_WPSCAN_MODULES_DIR + '/wp_themes'
|
||||
@themes_file = @fixtures_dir + '/themes.txt'
|
||||
@theme_vulns_file = @fixtures_dir + '/theme_vulns.xml'
|
||||
|
||||
@wp_url = 'http://example.localhost/'
|
||||
end
|
||||
|
||||
before :each do
|
||||
@module = WpScanModuleSpec.new(@wp_url)
|
||||
@module.error_404_hash = Digest::MD5.hexdigest('Error 404!')
|
||||
@module.extend(WpThemes)
|
||||
|
||||
@options = {
|
||||
base_url: @wp_url,
|
||||
only_vulnerable_ones: false,
|
||||
show_progression: false,
|
||||
error_404_hash: Digest::MD5.hexdigest('Error 404!'),
|
||||
vulns_file: @theme_vulns_file,
|
||||
file: @themes_file,
|
||||
type: 'themes',
|
||||
wp_content_dir: 'wp-content',
|
||||
vulns_xpath_2: '//theme'
|
||||
}
|
||||
File.exist?(@theme_vulns_file).should == true
|
||||
File.exist?(@themes_file).should == true
|
||||
@targets = [
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'zenpro/404.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'zenpro'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'zeta-zip/404.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'zeta-zip'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'zfirst/404.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'zfirst'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'zgrey/404.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'zgrey'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'zindi-ii/404.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'zindi-ii'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'zindi/404.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'zindi'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'zombie-apocalypse/404.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'zombie-apocalypse'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'zsofa/404.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'zsofa'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'zwei-seiten/404.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'zwei-seiten'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'twentyten/404.php',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'twentyten'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'shopperpress',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'shopperpress'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'wise',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'wise'
|
||||
}),
|
||||
WpTheme.new({
|
||||
base_url: 'http://example.localhost/',
|
||||
path: 'webfolio',
|
||||
wp_content_dir: 'wp-content',
|
||||
name: 'webfolio'
|
||||
})
|
||||
]
|
||||
end
|
||||
|
||||
describe '#themes_from_passive_detection' do
|
||||
let(:passive_detection_fixtures) { @fixtures_dir + '/passive_detection' }
|
||||
|
||||
it 'should return an empty array' do
|
||||
stub_request_to_fixture(url: @module.url, fixture: File.new(passive_detection_fixtures + '/no_theme.htm'))
|
||||
themes = @module.themes_from_passive_detection(base_url: @module.url, wp_content_dir: 'wp-content')
|
||||
themes.should be_empty
|
||||
end
|
||||
|
||||
it 'should return the expected themes' do
|
||||
stub_request_to_fixture(url: @module.url, fixture: File.new(passive_detection_fixtures + '/various_themes.htm'))
|
||||
|
||||
expected_theme_names = %w{ theme1 theme2 theme3 }
|
||||
expected_themes = []
|
||||
expected_theme_names.each do |theme_name|
|
||||
expected_themes << WpTheme.new(
|
||||
base_url: @module.url,
|
||||
path: "/themes/#{theme_name}/",
|
||||
name: theme_name
|
||||
)
|
||||
end
|
||||
|
||||
themes = @module.themes_from_passive_detection(base_url: @module.url, wp_content_dir: 'wp-content')
|
||||
themes.should_not be_empty
|
||||
themes.length.should == expected_themes.length
|
||||
themes.sort.should == expected_themes.sort
|
||||
end
|
||||
end
|
||||
|
||||
describe '#themes_from_aggressive_detection' do
|
||||
|
||||
before :each do
|
||||
stub_request(:get, @module.uri.to_s).to_return(status: 200)
|
||||
# Point all targets to a 404
|
||||
@targets.each do |target|
|
||||
stub_request(:get, target.get_full_url.to_s).to_return(status: 404)
|
||||
# to_s calls readme_url
|
||||
stub_request(:get, target.readme_url.to_s).to_return(status: 404)
|
||||
end
|
||||
end
|
||||
|
||||
after :each do
|
||||
@passive_detection_fixture = SPEC_FIXTURES_DIR + '/empty-file' unless @passive_detection_fixture
|
||||
stub_request_to_fixture(url: "#{@module.uri}/".sub(/\/\/$/, '/'), fixture: @passive_detection_fixture)
|
||||
detected = @module.themes_from_aggressive_detection(@options)
|
||||
detected.length.should == @expected_themes.length
|
||||
detected.sort.should == @expected_themes.sort
|
||||
end
|
||||
|
||||
it 'should return an empty array' do
|
||||
@expected_themes = []
|
||||
end
|
||||
|
||||
it 'should return an array with 3 WpTheme (1 detected from passive method)' do
|
||||
@passive_detection_fixture = @fixtures_dir + '/passive_detection/one_theme.htm'
|
||||
@expected_themes = @targets.sample(2)
|
||||
@expected_themes.each do |p|
|
||||
stub_request(:get, p.get_full_url.to_s).to_return(status: 200)
|
||||
end
|
||||
new_theme = WpTheme.new(
|
||||
base_url: 'http://example.localhost/',
|
||||
path: '/themes/custom-twentyten/',
|
||||
name: 'custom-twentyten'
|
||||
)
|
||||
stub_request(:get, new_theme.readme_url.to_s).to_return(status: 200)
|
||||
@expected_themes << new_theme
|
||||
end
|
||||
|
||||
# testing response codes
|
||||
WpTarget.valid_response_codes.each do |valid_response_code|
|
||||
it "should detect the theme if the reponse.code is #{valid_response_code}" do
|
||||
@expected_themes = []
|
||||
theme_url = [@targets.sample(1)[0]]
|
||||
theme_url.should_not be_nil
|
||||
theme_url.length.should == 1
|
||||
@expected_themes = theme_url
|
||||
stub_request(:get, theme_url[0].get_full_url.to_s).to_return(status: valid_response_code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,115 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
shared_examples_for 'WpTimthumbs' do
|
||||
|
||||
before :each do
|
||||
@options = {}
|
||||
@url = 'http://example.localhost/'
|
||||
@theme_name = 'bueno'
|
||||
@options[:base_url] = @url
|
||||
@options[:wp_content_dir] = 'wp-content'
|
||||
@options[:name] = @theme_name
|
||||
@options[:error_404_hash] = 'xx'
|
||||
@options[:show_progression] = false
|
||||
@options[:only_vulnerable_ones] = false
|
||||
@options[:vulns_file] = 'xx'
|
||||
@options[:type] = 'timthumbs'
|
||||
@module = WpScanModuleSpec.new(@url)
|
||||
@fixtures_dir = SPEC_FIXTURES_WPSCAN_MODULES_DIR + '/wp_timthumbs'
|
||||
@timthumbs_file = @fixtures_dir + '/timthumbs.txt'
|
||||
@targets_from_file = %w{
|
||||
http://example.localhost/wp-content/plugins/fotoslide/timthumb.php
|
||||
http://example.localhost/wp-content/plugins/feature-slideshow/timthumb.php
|
||||
}
|
||||
@targets_from_theme =
|
||||
[
|
||||
'http://example.localhost/wp-content/themes/' + @theme_name + '/timthumb.php',
|
||||
'http://example.localhost/wp-content/themes/' + @theme_name + '/lib/timthumb.php',
|
||||
'http://example.localhost/wp-content/themes/' + @theme_name + '/inc/timthumb.php',
|
||||
'http://example.localhost/wp-content/themes/' + @theme_name + '/includes/timthumb.php',
|
||||
'http://example.localhost/wp-content/themes/' + @theme_name + '/scripts/timthumb.php',
|
||||
'http://example.localhost/wp-content/themes/' + @theme_name + '/tools/timthumb.php',
|
||||
'http://example.localhost/wp-content/themes/' + @theme_name + '/functions/timthumb.php'
|
||||
]
|
||||
|
||||
@module.extend(WpTimthumbs)
|
||||
end
|
||||
|
||||
describe '#targets_url_from_theme' do
|
||||
it 'should return the targets for the theme' do
|
||||
targets = @module.send(:targets_url_from_theme, @theme_name, @options)
|
||||
|
||||
targets.should_not be_empty
|
||||
targets.length.should > 0
|
||||
temp = []
|
||||
targets.each do |t|
|
||||
temp << t.get_full_url.to_s
|
||||
end
|
||||
temp.sort.should === @targets_from_theme.sort
|
||||
end
|
||||
end
|
||||
|
||||
describe '#timthumbs and #has_timthumbs?' do
|
||||
before :each do
|
||||
@options[:file] = @timthumbs_file
|
||||
@targets_from_file.each do |url|
|
||||
stub_request(:get, url).to_return(status: 404)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return an empty array' do
|
||||
timthumbs = @module.timthumbs(nil, @options)
|
||||
timthumbs.should be_empty
|
||||
@module.has_timthumbs?(nil, @options).should be_false
|
||||
end
|
||||
|
||||
it 'should return an array with 7 elements (from passive detection)' do
|
||||
stub_request(:get, %r{http://example\.localhost/wp-content/themes/my-theme/.*}).to_return(status: 200)
|
||||
timthumbs = @module.timthumbs('my-theme', @options)
|
||||
timthumbs.length.should == 7
|
||||
end
|
||||
|
||||
it 'should return an array with 2 timthumbs url' do
|
||||
expected = []
|
||||
urls = []
|
||||
urls_hash = WpEnumerator.generate_items(@options)
|
||||
urls_hash.each do |u|
|
||||
url = u.get_full_url.to_s
|
||||
urls << url
|
||||
stub_request(:get, url).to_return(status: 404)
|
||||
end
|
||||
urls.sample(2).each do |target_url|
|
||||
expected << target_url
|
||||
stub_request(:get, target_url).
|
||||
to_return(status: 200, body: File.new(@fixtures_dir + '/timthumb.php'))
|
||||
end
|
||||
|
||||
timthumbs = @module.timthumbs(nil, @options)
|
||||
timthumbs.should_not be_empty
|
||||
|
||||
temp = []
|
||||
timthumbs.each do |t|
|
||||
temp << t.get_full_url.to_s
|
||||
end
|
||||
temp.sort.should === expected.sort
|
||||
@module.has_timthumbs?(nil).should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,279 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
shared_examples_for 'WpUsernames' do
|
||||
|
||||
before :each do
|
||||
@target_url = 'http://example.localhost/'
|
||||
@module = WpScanModuleSpec.new(@target_url)
|
||||
@fixtures_dir = SPEC_FIXTURES_WPSCAN_MODULES_DIR + '/wp_usernames'
|
||||
|
||||
@module.extend(WpUsernames)
|
||||
end
|
||||
|
||||
describe '#author_url' do
|
||||
it 'should return the auhor url according to his id' do
|
||||
@module.author_url(1).should === "#@target_url?author=1"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#usernames' do
|
||||
before :each do
|
||||
(1..10).each do |index|
|
||||
stub_request(:get, @module.author_url(index)).to_return(status: 404)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return an empty array' do
|
||||
@module.usernames.should be_empty
|
||||
end
|
||||
|
||||
it 'should return an array with 1 username (from header location)' do
|
||||
stub_request(:get, @module.author_url(3)).
|
||||
to_return(status: 301, headers: {'location' => '/author/Youhou'})
|
||||
|
||||
usernames = @module.usernames
|
||||
usernames.should_not be_empty
|
||||
usernames.length.should == 1
|
||||
usernames[0].id.should == 3
|
||||
usernames[0].name.should == 'Youhou'
|
||||
usernames[0].nickname.should == 'empty'
|
||||
end
|
||||
|
||||
it 'should return an array with 1 username (from in the body response)' do
|
||||
stub_request(:get, @module.author_url(2)).
|
||||
to_return(status: 200, body: File.new(@fixtures_dir + '/test.html'))
|
||||
|
||||
usernames = @module.usernames(range: (1..2))
|
||||
usernames.should_not be_empty
|
||||
usernames.should === ([WpUser.new('test', 2, 'first last | user's Blog!')])
|
||||
end
|
||||
|
||||
it 'should return an array with 2 usernames (one is a duplicate and should not be present twice)' do
|
||||
stub_request(:get, @module.author_url(4)).
|
||||
to_return(status: 301, headers: {'location' => '/author/Youhou/'})
|
||||
|
||||
stub_request(:get, @module.author_url(2)).
|
||||
to_return(status: 200, body: File.new(@fixtures_dir + '/test.html'))
|
||||
|
||||
usernames = @module.usernames(range: (1..5))
|
||||
usernames.should_not be_empty
|
||||
expected = [
|
||||
WpUser.new('test', 2, 'first last | user's Blog!'),
|
||||
WpUser.new('Youhou', 4, 'empty')
|
||||
]
|
||||
|
||||
usernames.sort_by { |u| u.name }.should === expected.sort_by { |u| u.name }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_nickname_from_url' do
|
||||
after :each do
|
||||
url = 'http://example.localhost/'
|
||||
stub_request(:get, url).to_return(status: @status, body: @content)
|
||||
username = @module.get_nickname_from_url(url)
|
||||
username.should === @expected
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
@status = 200
|
||||
@content = ''
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
@status = 400
|
||||
@content = ''
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return admin' do
|
||||
@status = 200
|
||||
@content = '<title>admin</title>'
|
||||
@expected = 'admin'
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
@status = 201
|
||||
@content = '<title>admin</title>'
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_nickname_from_response' do
|
||||
after :each do
|
||||
url = 'http://example.localhost/'
|
||||
stub_request(:get, url).to_return(status: @status, body: @content)
|
||||
resp = Browser.instance.get(url)
|
||||
nickname = @module.get_nickname_from_response(resp)
|
||||
nickname.should === @expected
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
@status = 200
|
||||
@content = ''
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
@status = 400
|
||||
@content = ''
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return admin' do
|
||||
@status = 200
|
||||
@content = '<title>admin</title>'
|
||||
@expected = 'admin'
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
@status = 201
|
||||
@content = '<title>admin</title>'
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_username_from_response' do
|
||||
after :each do
|
||||
url = @module.url
|
||||
stub_request_to_fixture(url: url, fixture: File.new(@fixtures_dir + @file))
|
||||
resp = Browser.instance.get(url)
|
||||
username = @module.get_username_from_response(resp)
|
||||
username.should === @expected
|
||||
end
|
||||
|
||||
# No Permalinks
|
||||
it 'should return admin' do
|
||||
@file = '/admin.html'
|
||||
@expected = 'admin'
|
||||
end
|
||||
|
||||
# With Permalinks
|
||||
it 'should return test' do
|
||||
@file = '/test.html'
|
||||
@expected = 'test'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#extract_nickname_from_body' do
|
||||
after :each do
|
||||
result = @module.extract_nickname_from_body(@body)
|
||||
result.should === @expected
|
||||
end
|
||||
|
||||
it 'should return admin' do
|
||||
@body = '<title>admin</title>'
|
||||
@expected = 'admin'
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
@body = '<title>adm<in</title>'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
@body = '<titler>admin</titler>'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return admin | ' do
|
||||
@body = '<title>admin | </title>'
|
||||
@expected = 'admin | '
|
||||
end
|
||||
|
||||
it 'should return an empty string' do
|
||||
@body = '<title></title>'
|
||||
@expected = ''
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove_junk_from_nickname' do
|
||||
it 'should throw an exception' do
|
||||
@input = nil
|
||||
expect { @module.remove_junk_from_nickname(@input) }.to raise_error(RuntimeError, 'Need an array as input')
|
||||
end
|
||||
|
||||
it 'should not throw an exception' do
|
||||
@input = []
|
||||
expect { @module.remove_junk_from_nickname(@input) }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'should throw an exception' do
|
||||
@input = [WpOptions.new]
|
||||
expect { @module.remove_junk_from_nickname(@input) }.to raise_error(RuntimeError, 'Items must be of type WpUser')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove_junk_from_nickname' do
|
||||
after :each do
|
||||
result = @module.remove_junk_from_nickname(@input)
|
||||
result.should === @expected
|
||||
end
|
||||
|
||||
it 'should return an empty array' do
|
||||
@input = []
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return input object' do
|
||||
@input = [WpUser.new(nil, nil, nil)]
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return input object' do
|
||||
@input = [WpUser.new('', '', '')]
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should remove asdf' do
|
||||
@input = [WpUser.new(nil, nil, 'lkjh asdf'), WpUser.new(nil, nil, 'ijrjd asdf')]
|
||||
@expected = [WpUser.new(nil, nil, 'lkjh'), WpUser.new(nil, nil, 'ijrjd')]
|
||||
end
|
||||
|
||||
it 'should return unmodified input object' do
|
||||
@input = [WpUser.new(nil, nil, 'lkjh asdfa'), WpUser.new(nil, nil, 'ijrjd asdf')]
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return input object' do
|
||||
@input = [WpUser.new(nil, nil, 'lkjh asdf')]
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return lkhj asdf' do
|
||||
@input = [WpUser.new(nil, nil, 'lkhj asdf'), WpUser.new(nil, nil, 'lkhj asdf')]
|
||||
@expected = [WpUser.new(nil, nil, ''), WpUser.new(nil, nil, '')]
|
||||
end
|
||||
end
|
||||
|
||||
# Issue 66
|
||||
describe '#remove_junk_from_nickname' do
|
||||
it 'should contain the string empty' do
|
||||
input = [WpUser.new('admin', 1, 'admin | Wordpress 3.4.2'), WpUser.new('', 2, 'Wordpress 3.4.2')]
|
||||
result = @module.remove_junk_from_nickname(input)
|
||||
result[0].nickname.should === 'admin | '
|
||||
result[0].name.should === 'admin'
|
||||
result[0].id.should === 1
|
||||
result[1].nickname.should === 'empty'
|
||||
result[1].name.should === 'empty'
|
||||
result[1].id.should === 2
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
|
||||
describe WpDetector do
|
||||
# TODO
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
|
||||
describe WpEnumerator do
|
||||
# TODO
|
||||
end
|
||||
@@ -1,587 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
|
||||
describe WpPlugin do
|
||||
|
||||
describe '#initialize' do
|
||||
it 'should create a correct instance' do
|
||||
instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins'
|
||||
)
|
||||
instance.wp_content_dir.should == 'wp-content'
|
||||
instance.base_url.should == 'http://sub.example.com/path/to/wordpress/'
|
||||
instance.path.should == 'test/asdf.php'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_full_url' do
|
||||
after :each do
|
||||
arguments = {
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins',
|
||||
wp_content_dir: @wp_content_dir
|
||||
}
|
||||
|
||||
instance = WpItem.new(arguments)
|
||||
instance.get_full_url.to_s.should === @expected
|
||||
end
|
||||
|
||||
it 'should return the correct url' do
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/wp-content/plugins/test/asdf.php'
|
||||
end
|
||||
|
||||
it 'should return the correct url (custom wp_content_dir)' do
|
||||
@wp_content_dir = 'custom'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/custom/plugins/test/asdf.php'
|
||||
end
|
||||
|
||||
it 'should trim / and add missing / before concatenating url' do
|
||||
@wp_content_dir = '/custom/'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/custom/plugins/test/asdf.php'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_url_without_filename' do
|
||||
after :each do
|
||||
arguments = {
|
||||
base_url: @base_url || 'http://sub.example.com/path/to/wordpress/',
|
||||
path: @path || 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins',
|
||||
wp_content_dir: @wp_content_dir
|
||||
}
|
||||
|
||||
instance = WpItem.new(arguments)
|
||||
instance.get_url_without_filename.to_s.should === @expected
|
||||
end
|
||||
|
||||
it 'should return the correct url' do
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/wp-content/plugins/test/'
|
||||
end
|
||||
|
||||
it 'should return the correct url (custom wp_content_dir)' do
|
||||
@wp_content_dir = 'custom'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/custom/plugins/test/'
|
||||
end
|
||||
|
||||
it 'should trim / and add missing / before concatenating url' do
|
||||
@wp_content_dir = '/custom/'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/custom/plugins/test/'
|
||||
end
|
||||
|
||||
it 'should not remove the last foldername' do
|
||||
@path = 'test/'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/wp-content/plugins/test/'
|
||||
end
|
||||
|
||||
it 'should return the correct url (https)' do
|
||||
@base_url = 'https://sub.example.com/path/to/wordpress/'
|
||||
@expected = 'https://sub.example.com/path/to/wordpress/wp-content/plugins/test/'
|
||||
end
|
||||
|
||||
it "should add the last slash if it's not present" do
|
||||
@path = 'test-one'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/wp-content/plugins/test-one/'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#version' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_PLUGIN_DIR + '/version' }
|
||||
|
||||
before :each do
|
||||
@instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins'
|
||||
)
|
||||
end
|
||||
|
||||
it 'should return a version number' do
|
||||
stub_request(:get, @instance.readme_url.to_s).to_return(status: 200, body: 'Stable tag: 1.2.4.3.2.1')
|
||||
@instance.version.should == '1.2.4.3.2.1'
|
||||
end
|
||||
|
||||
it 'should not return a version number' do
|
||||
stub_request(:get, @instance.readme_url.to_s).to_return(status: 200, body: 'Stable tag: trunk')
|
||||
@instance.version.should be nil
|
||||
end
|
||||
|
||||
it 'should return nil if the version is invalid (IE : trunk etc)' do
|
||||
stub_request_to_fixture(url: @instance.readme_url.to_s, fixture: fixtures_dir + '/trunk-version.txt')
|
||||
@instance.version.should be_nil
|
||||
end
|
||||
|
||||
it 'should return the version 0.4' do
|
||||
stub_request_to_fixture(url: @instance.readme_url.to_s, fixture: fixtures_dir + '/simple-login-lockdown-0.4.txt')
|
||||
@instance.version.should === '0.4'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#directory_listing?' do
|
||||
before :each do
|
||||
@instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins'
|
||||
)
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
stub_request(:get, @instance.get_url_without_filename.to_s)
|
||||
.to_return(status: 200, body: '<html><head><title>Index of asdf</title></head></html>')
|
||||
|
||||
@instance.directory_listing?.should == true
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
stub_request(:get, @instance.get_url_without_filename.to_s)
|
||||
.to_return(status: 200, body: '<html><head><title>My Wordpress Site</title></head></html>')
|
||||
|
||||
@instance.directory_listing?.should == false
|
||||
end
|
||||
|
||||
it 'should return false on a 404' do
|
||||
stub_request(:get, @instance.get_url_without_filename.to_s.to_s).to_return(status: 404)
|
||||
@instance.directory_listing?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#extract_name_from_url' do
|
||||
after :each do
|
||||
arguments = {
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: @path || 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: @type || 'plugins',
|
||||
wp_content_dir: @wp_content_dir
|
||||
}
|
||||
|
||||
instance = WpItem.new(arguments)
|
||||
instance.extract_name_from_url.should === @expected
|
||||
end
|
||||
|
||||
it 'should extract the correct name' do
|
||||
@expected = 'test'
|
||||
end
|
||||
|
||||
it 'should extract the correct name (custom wp_content_dir)' do
|
||||
@wp_content_dir = 'custom'
|
||||
@expected = 'test'
|
||||
end
|
||||
|
||||
it 'should extract the correct name' do
|
||||
@path = 'test2/asdf.php'
|
||||
@wp_content_dir = '/custom/'
|
||||
@expected = 'test2'
|
||||
end
|
||||
|
||||
it 'should extract the correct plugin name' do
|
||||
@path = 'testplugin/'
|
||||
@expected = 'testplugin'
|
||||
end
|
||||
|
||||
it 'should extract the correct theme name' do
|
||||
@path = 'testtheme/'
|
||||
@type = 'themes'
|
||||
@expected = 'testtheme'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_s' do
|
||||
before :each do
|
||||
@instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins'
|
||||
)
|
||||
end
|
||||
|
||||
it 'should return the name including a version number' do
|
||||
stub_request(:get, @instance.readme_url.to_s).to_return(status: 200, body: 'Stable tag: 1.2.4.3.2.1')
|
||||
@instance.to_s.should == 'test v1.2.4.3.2.1'
|
||||
end
|
||||
|
||||
it 'should not return the name without a version number' do
|
||||
stub_request(:get, @instance.readme_url.to_s).to_return(status: 200, body: 'Stable tag: trunk')
|
||||
@instance.to_s.should == 'test'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#==' do
|
||||
before :each do
|
||||
@instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins'
|
||||
)
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
instance2 = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'newname/asdf.php',
|
||||
type: 'plugins',
|
||||
vulns_file: 'XXX.xml',
|
||||
vulns_xpath: 'XX'
|
||||
)
|
||||
(@instance == instance2).should == false
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
instance2 = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
type: 'plugins',
|
||||
vulns_file: 'XXX.xml',
|
||||
vulns_xpath: 'XX'
|
||||
)
|
||||
(@instance == instance2).should == true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_sub_folder' do
|
||||
after :each do
|
||||
arguments = {
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
wp_content_dir: 'wp-content',
|
||||
wp_plugins_dir: 'wp-content/plugins',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: @type || 'themes'
|
||||
}
|
||||
|
||||
instance = WpItem.new(arguments)
|
||||
|
||||
if @raise_error
|
||||
expect { instance.get_sub_folder }.to @raise_error
|
||||
else
|
||||
instance.get_sub_folder.should === @expected
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return themes' do
|
||||
@expected = 'themes'
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
@type = 'timthumbs'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should raise an exception' do
|
||||
@type = 'type'
|
||||
@raise_error = raise_error(RuntimeError, 'unknown type type')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#readme_url' do
|
||||
after :each do
|
||||
arguments = {
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: @type || 'plugins',
|
||||
wp_content_dir: @wp_content_dir
|
||||
}
|
||||
|
||||
instance = WpItem.new(arguments)
|
||||
instance.readme_url.to_s.should === @expected
|
||||
end
|
||||
|
||||
it 'should return the corrent plugin readme url' do
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/wp-content/plugins/test/readme.txt'
|
||||
end
|
||||
|
||||
it 'should return the corrent plugin readme url (custom wp_content)' do
|
||||
@wp_content_dir = 'custom'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/custom/plugins/test/readme.txt'
|
||||
end
|
||||
|
||||
it 'should return the corrent theme readme url' do
|
||||
@type = 'themes'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/wp-content/themes/test/readme.txt'
|
||||
end
|
||||
|
||||
it 'should return the corrent theme readme url (custom wp_content)' do
|
||||
@type = 'themes'
|
||||
@wp_content_dir = 'custom'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/custom/themes/test/readme.txt'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#changelog_url' do
|
||||
after :each do
|
||||
arguments = {
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: @type || 'plugins',
|
||||
wp_content_dir: @wp_content_dir
|
||||
}
|
||||
|
||||
instance = WpItem.new(arguments)
|
||||
instance.changelog_url.to_s.should === @expected
|
||||
end
|
||||
|
||||
it 'should return the corrent plugin changelog url' do
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/wp-content/plugins/test/changelog.txt'
|
||||
end
|
||||
|
||||
it 'should return the corrent plugin changelog url (custom wp_content)' do
|
||||
@wp_content_dir = 'custom'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/custom/plugins/test/changelog.txt'
|
||||
end
|
||||
|
||||
it 'should return the corrent theme changelog url' do
|
||||
@type = 'themes'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/wp-content/themes/test/changelog.txt'
|
||||
end
|
||||
|
||||
it 'should return the corrent theme changelog url (custom wp_content)' do
|
||||
@type = 'themes'
|
||||
@wp_content_dir = 'custom'
|
||||
@expected = 'http://sub.example.com/path/to/wordpress/custom/themes/test/changelog.txt'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_readme?' do
|
||||
before :each do
|
||||
@instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins'
|
||||
)
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
stub_request(:get, @instance.readme_url.to_s).to_return(status: 200)
|
||||
@instance.has_readme?.should == true
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
stub_request(:get, @instance.readme_url.to_s).to_return(status: 403)
|
||||
@instance.has_readme?.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_changelog?' do
|
||||
before :each do
|
||||
@instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins'
|
||||
)
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
stub_request(:get, @instance.changelog_url.to_s).to_return(status: 200)
|
||||
@instance.has_changelog?.should == true
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
stub_request(:get, @instance.changelog_url.to_s).to_return(status: 403)
|
||||
@instance.has_changelog?.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#wp_org_url' do
|
||||
it 'sould return a themes url' do
|
||||
instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'themes'
|
||||
)
|
||||
instance.wp_org_url.to_s.should == 'http://wordpress.org/extend/themes/test/'
|
||||
end
|
||||
|
||||
it 'sould return a plugins url' do
|
||||
instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins'
|
||||
)
|
||||
instance.wp_org_url.to_s.should == 'http://wordpress.org/extend/plugins/test/'
|
||||
end
|
||||
|
||||
it 'sould raise an exception' do
|
||||
instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'invalid'
|
||||
)
|
||||
expect { instance.wp_org_url }.to raise_error(RuntimeError, 'No Wordpress URL for invalid')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#wp_org_item?' do
|
||||
it 'sould return true' do
|
||||
instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'w3-total-cache',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins'
|
||||
)
|
||||
instance.wp_org_item?.should be_true
|
||||
end
|
||||
|
||||
it 'sould return true' do
|
||||
instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'twentyten',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'themes'
|
||||
)
|
||||
instance.wp_org_item?.should be_true
|
||||
end
|
||||
|
||||
it 'sould return false' do
|
||||
instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'can_not_be_in_repository',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins'
|
||||
)
|
||||
instance.wp_org_item?.should be_false
|
||||
end
|
||||
|
||||
it 'sould return false' do
|
||||
instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'can_not_be_in_repository',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'themes'
|
||||
)
|
||||
instance.wp_org_item?.should be_false
|
||||
end
|
||||
|
||||
it 'sould raise an exception' do
|
||||
instance = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'test',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'invalid'
|
||||
)
|
||||
expect { instance.wp_org_item? }.to raise_error(RuntimeError, 'Unknown type invalid')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#error_log_url' do
|
||||
it 'should return a correct url' do
|
||||
temp = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'name/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'name',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins')
|
||||
temp.error_log_url.to_s.should == 'http://sub.example.com/path/to/wordpress/wp-content/plugins/name/error_log'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#error_log?' do
|
||||
before :each do
|
||||
@temp = WpItem.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
name: 'name',
|
||||
vulns_xpath: 'XX',
|
||||
type: 'plugins')
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
stub_request(:get, @temp.error_log_url.to_s).to_return(status: 200, body: 'PHP Fatal error')
|
||||
@temp.error_log?.should be true
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
stub_request(:get, @temp.error_log_url.to_s).to_return(status: 500, body: 'Access denied')
|
||||
@temp.error_log?.should be false
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
fixtures_dir = SPEC_FIXTURES_WPSCAN_WP_PLUGIN_DIR + '/error_log'
|
||||
stub_request(:get, @temp.error_log_url.to_s).to_return(
|
||||
status: 200,
|
||||
body: File.new(fixtures_dir + '/error_log')
|
||||
)
|
||||
|
||||
@temp.error_log?.should be true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,139 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
|
||||
describe WpOptions do
|
||||
describe '#check_options' do
|
||||
before :each do
|
||||
@options = {}
|
||||
@options[:base_url] = 'url'
|
||||
@options[:only_vulnerable_ones] = false
|
||||
@options[:file] = 'file'
|
||||
@options[:vulns_file] = 'vulns_file'
|
||||
@options[:vulns_xpath] = 'vulns_xpath'
|
||||
@options[:vulns_xpath_2] = 'vulns_xpath_2'
|
||||
@options[:wp_content_dir] = 'wp_content_dir'
|
||||
@options[:show_progression] = true
|
||||
@options[:error_404_hash] = 'error_404_hash'
|
||||
@options[:type] = 'type'
|
||||
|
||||
@message = ''
|
||||
end
|
||||
|
||||
after :each do
|
||||
expect { WpOptions.check_options(@options) }.to raise_error(RuntimeError, @message)
|
||||
end
|
||||
|
||||
it 'should raise an exception (base_url empty)' do
|
||||
@options[:base_url] = ''
|
||||
@message = 'base_url must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (base_url nil)' do
|
||||
@options[:base_url] = nil
|
||||
@message = 'base_url must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (only_vulnerable_ones nil)' do
|
||||
@options[:only_vulnerable_ones] = nil
|
||||
@message = 'only_vulnerable_ones must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (file empty)' do
|
||||
@options[:file] = ''
|
||||
@message = 'file must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (file nil)' do
|
||||
@options[:file] = nil
|
||||
@message = 'file must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (vulns_file empty)' do
|
||||
@options[:vulns_file] = ''
|
||||
@message = 'vulns_file must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (vulns_file nil)' do
|
||||
@options[:vulns_file] = nil
|
||||
@message = 'vulns_file must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (vulns_xpath empty)' do
|
||||
@options[:vulns_xpath] = ''
|
||||
@message = 'vulns_xpath must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (vulns_xpath nil)' do
|
||||
@options[:vulns_xpath] = nil
|
||||
@message = 'vulns_xpath must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (vulns_xpath_2 empty)' do
|
||||
@options[:vulns_xpath_2] = ''
|
||||
@message = 'vulns_xpath_2 must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (vulns_xpath_2 nil)' do
|
||||
@options[:vulns_xpath_2] = nil
|
||||
@message = 'vulns_xpath_2 must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (wp_content_dir empty)' do
|
||||
@options[:wp_content_dir] = ''
|
||||
@message = 'wp_content_dir must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (wp_content_dir nil)' do
|
||||
@options[:wp_content_dir] = nil
|
||||
@message = 'wp_content_dir must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (show_progression nil)' do
|
||||
@options[:show_progression] = nil
|
||||
@message = 'show_progression must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (error_404_hash empty)' do
|
||||
@options[:error_404_hash] = ''
|
||||
@message = 'error_404_hash must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (error_404_hash nil)' do
|
||||
@options[:error_404_hash] = nil
|
||||
@message = 'error_404_hash must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (type empty)' do
|
||||
@options[:type] = ''
|
||||
@message = 'type must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (type nil)' do
|
||||
@options[:type] = nil
|
||||
@message = 'type must be set'
|
||||
end
|
||||
|
||||
it 'should raise an exception (type unknown)' do
|
||||
@options[:type] = 'unknown'
|
||||
@message = 'Unknown type unknown'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,44 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
|
||||
describe WpPlugin do
|
||||
describe '#initialize' do
|
||||
it 'should not raise an exception' do
|
||||
expect { WpPlugin.new(base_url: 'url', path: 'path', wp_content_dir: 'dir', name: 'name') }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'should not raise an exception (wp_content_dir not set)' do
|
||||
expect { WpPlugin.new(base_url: 'url', path: 'path', name: 'name') }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'should raise an exception (base_url not set)' do
|
||||
expect { WpPlugin.new(path: 'path', wp_content_dir: 'dir', name: 'name') }.to raise_error
|
||||
end
|
||||
|
||||
it 'should raise an exception (path not set)' do
|
||||
expect { WpPlugin.new(base_url: 'url', wp_content_dir: 'dir', name: 'name') }.to raise_error
|
||||
end
|
||||
|
||||
it 'should raise an exception (name not set)' do
|
||||
expect { WpPlugin.new(base_url: 'url', path: 'path', wp_content_dir: 'dir') }.to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -41,10 +41,6 @@ describe WpTarget do
|
||||
it_should_behave_like 'WpLoginProtection'
|
||||
it_should_behave_like 'Malwares'
|
||||
it_should_behave_like 'BruteForce'
|
||||
it_should_behave_like 'WpUsernames'
|
||||
it_should_behave_like 'WpTimthumbs'
|
||||
it_should_behave_like 'WpPlugins'
|
||||
it_should_behave_like 'WpThemes'
|
||||
|
||||
describe '#initialize' do
|
||||
it 'should raise an error if the target_url is nil or empty' do
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
|
||||
describe WpTheme do
|
||||
before :all do
|
||||
@target_uri = URI.parse('http://example.localhost/')
|
||||
|
||||
Browser.instance(
|
||||
config_file: SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf.json',
|
||||
cache_timeout: 0
|
||||
)
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'should not raise an exception' do
|
||||
expect { WpTheme.new(base_url: 'url', path: 'path', wp_content_dir: 'dir', name: 'name') }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'should not raise an exception (wp_content_dir not set)' do
|
||||
expect { WpTheme.new(base_url: 'url', path: 'path', name: 'name') }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'should raise an exception (base_url not set)' do
|
||||
expect { WpTheme.new(path: 'path', wp_content_dir: 'dir', name: 'name') }.to raise_error
|
||||
end
|
||||
|
||||
it 'should raise an exception (path not set)' do
|
||||
expect { WpTheme.new(base_url: 'url', wp_content_dir: 'dir', name: 'name') }.to raise_error
|
||||
end
|
||||
|
||||
it 'should raise an exception (name not set)' do
|
||||
expect { WpTheme.new(base_url: 'url', path: 'path', wp_content_dir: 'dir') }.to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_from_css_link' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_THEME_DIR + '/find/css_link' }
|
||||
|
||||
after :each do
|
||||
if @expected_name
|
||||
stub_request_to_fixture(url: @target_uri.to_s, fixture: @fixture)
|
||||
|
||||
wp_theme = WpTheme.find_from_css_link(@target_uri)
|
||||
wp_theme.should be_a WpTheme
|
||||
wp_theme.name.should === @expected_name
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return nil if no theme is present' do
|
||||
stub_request(:get, @target_uri.to_s).to_return(status: 200, body: '')
|
||||
|
||||
WpTheme.find_from_css_link(@target_uri).should be_nil
|
||||
end
|
||||
|
||||
it 'should return a WpTheme object with .name = twentyeleven' do
|
||||
@fixture = fixtures_dir + '/wordpress-twentyeleven.htm'
|
||||
@expected_name = 'twentyeleven'
|
||||
end
|
||||
|
||||
# http://code.google.com/p/wpscan/issues/detail?id=131
|
||||
# Theme name with spaces raises bad URI(is not URI?)
|
||||
it 'should not raise an error if the theme name has spaces or special chars' do
|
||||
@fixture = fixtures_dir + '/theme-name-with-spaces.html'
|
||||
@expected_name = 'Copia di simplefolio'
|
||||
end
|
||||
|
||||
# https://github.com/wpscanteam/wpscan/issues/18
|
||||
it 'should get the theme if the <link> is inline with some other tags' do
|
||||
@fixture = fixtures_dir + '/inline_link_tag.html'
|
||||
@expected_name = 'inline'
|
||||
end
|
||||
|
||||
it 'should get the theme name even if relative URLs are used' do
|
||||
@fixture = fixtures_dir + '/relative_urls.html'
|
||||
@expected_name = 'theme_name'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_from_wooframework' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_THEME_DIR + '/find/wooframework' }
|
||||
|
||||
after :each do
|
||||
stub_request_to_fixture(url: @target_uri.to_s, fixture: @fixture)
|
||||
|
||||
wp_theme = WpTheme.find_from_wooframework(@target_uri)
|
||||
|
||||
stub_request(:get, wp_theme.default_style_url.to_s).to_return(status: 200)
|
||||
stub_request(:get, wp_theme.readme_url.to_s).to_return(status: 200)
|
||||
|
||||
wp_theme.should be_a WpTheme unless wp_theme.nil?
|
||||
wp_theme.should === @expected_theme
|
||||
end
|
||||
|
||||
it "should return a WpTheme object with .name 'Editorial' and .version '1.3.5'" do
|
||||
@fixture = fixtures_dir + '/editorial-1.3.5.html'
|
||||
@expected_theme = WpTheme.new(name: 'Editorial', version: '1.3.5', base_url: 'http://example.localhost/', path: 'Editorial')
|
||||
end
|
||||
|
||||
it "should return a WpTheme object with .name 'Merchant'" do
|
||||
@fixture = fixtures_dir + '/merchant-no-version.html'
|
||||
@expected_theme = WpTheme.new(name: 'Merchant', base_url: 'http://example.localhost/', path: 'Merchant')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_THEME_DIR + '/find' }
|
||||
|
||||
after :each do
|
||||
stub_request_to_fixture(url: @target_uri.to_s, fixture: @fixture)
|
||||
|
||||
wp_theme = WpTheme.find(@target_uri)
|
||||
|
||||
if @expected_name
|
||||
wp_theme.should be_a WpTheme
|
||||
wp_theme.name.should === @expected_name
|
||||
else
|
||||
wp_theme.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return nil if no theme is found' do
|
||||
@fixture = SPEC_FIXTURES_DIR + '/empty-file'
|
||||
@expected_name = nil
|
||||
end
|
||||
|
||||
it "should return a WpTheme object with .name 'twentyeleven'" do
|
||||
@fixture = fixtures_dir + '/css_link/wordpress-twentyeleven.htm'
|
||||
@expected_name = 'twentyeleven'
|
||||
end
|
||||
|
||||
it "should a WpTheme object with .name 'Merchant'" do
|
||||
@fixture = fixtures_dir + '/wooframework/merchant-no-version.html'
|
||||
@expected_name = 'Merchant'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#version' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_THEME_DIR + '/version' }
|
||||
let(:theme_style_url) { @target_uri.merge('wp-content/themes/spec-theme/style.css').to_s }
|
||||
|
||||
after :each do
|
||||
if @fixture
|
||||
stub_request_to_fixture(url: theme_style_url, fixture: @fixture)
|
||||
|
||||
wp_theme = WpTheme.new(name: 'spec-theme', style_url: theme_style_url, base_url: 'http://example.localhost/', path: 'spec-theme')
|
||||
|
||||
stub_request(:get, wp_theme.readme_url.to_s).to_return(status: 200)
|
||||
|
||||
wp_theme.version.should === @expected
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return nil if the version is not found' do
|
||||
@fixture = fixtures_dir + '/twentyeleven-unknow.css'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil if the style_url is nil' do
|
||||
wp_theme = WpTheme.new(name: 'hello-world', base_url: 'http://example.localhost/', path: 'hello-world')
|
||||
stub_request(:get, wp_theme.default_style_url.to_s).to_return(status: 200)
|
||||
stub_request(:get, wp_theme.readme_url.to_s).to_return(status: 200)
|
||||
wp_theme.version.should be_nil
|
||||
end
|
||||
|
||||
it 'should return 1.3' do
|
||||
@fixture = fixtures_dir + '/twentyeleven-1.3.css'
|
||||
@expected = '1.3'
|
||||
end
|
||||
|
||||
it 'should return 1.5.1' do
|
||||
@fixture = fixtures_dir + '/bueno-1.5.1.css'
|
||||
@expected = '1.5.1'
|
||||
end
|
||||
|
||||
it 'should get the version from default style.css url' do
|
||||
wp_theme = WpTheme.new(name: 'hello-world', base_url: 'http://example.localhost/', path: 'hello-world')
|
||||
stub_request(:get, wp_theme.default_style_url.to_s).to_return(status: 200, body: 'Version: 1.3.4.5')
|
||||
stub_request(:get, wp_theme.readme_url.to_s).to_return(status: 404)
|
||||
wp_theme.version.should === '1.3.4.5'
|
||||
end
|
||||
|
||||
it 'should get the version from custom style.css url' do
|
||||
style_url = 'http://example.localhost/custom_style.css'
|
||||
wp_theme = WpTheme.new(name: 'hello-world', base_url: 'http://example.localhost/', path: 'hello-world', style_url: style_url)
|
||||
stub_request(:get, style_url).to_return(status: 200, body: 'Version: 1.3.4.5')
|
||||
stub_request(:get, wp_theme.readme_url.to_s).to_return(status: 404)
|
||||
wp_theme.version.should === '1.3.4.5'
|
||||
end
|
||||
|
||||
it 'should get the version from readme.txt' do
|
||||
wp_theme = WpTheme.new(name: 'hello-world', base_url: 'http://example.localhost/', path: 'hello-world')
|
||||
stub_request(:get, wp_theme.default_style_url.to_s).to_return(status: 404)
|
||||
stub_request(:get, wp_theme.readme_url.to_s).to_return(status: 200, body: 'Stable Tag: 1.2.3.4')
|
||||
wp_theme.version.should === '1.2.3.4'
|
||||
end
|
||||
|
||||
it 'should get the version from readme.txt' do
|
||||
wp_theme = WpTheme.new(name: 'hello-world', base_url: 'http://example.localhost/', path: 'hello-world')
|
||||
stub_request(:get, wp_theme.default_style_url.to_s).to_return(status: 200)
|
||||
stub_request(:get, wp_theme.readme_url.to_s).to_return(status: 200, body: 'Stable Tag: 1.2.3.4')
|
||||
wp_theme.version.should === '1.2.3.4'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#===' do
|
||||
it 'should return false (name not equal)' do
|
||||
instance = WpTheme.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'themes/name/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
version: '1.0'
|
||||
)
|
||||
instance2 = WpTheme.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'themes/newname/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
version: '1.0'
|
||||
)
|
||||
(instance === instance2).should == false
|
||||
end
|
||||
|
||||
it 'should return false (version not equal)' do
|
||||
instance = WpTheme.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'themes/name/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
version: '1.0'
|
||||
)
|
||||
instance2 = WpTheme.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'themes/name/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
version: '2.0'
|
||||
)
|
||||
(instance === instance2).should == false
|
||||
end
|
||||
|
||||
it 'should return false (version and name not equal)' do
|
||||
instance = WpTheme.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'themes/name/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
version: '1.0'
|
||||
)
|
||||
instance2 = WpTheme.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'themes/newname/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
version: '2.0'
|
||||
)
|
||||
(instance === instance2).should == false
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
instance = WpTheme.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'themes/test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
version: '1.0'
|
||||
)
|
||||
instance2 = WpTheme.new(
|
||||
base_url: 'http://sub.example.com/path/to/wordpress/',
|
||||
path: 'themes/test/asdf.php',
|
||||
vulns_file: 'XXX.xml',
|
||||
version: '1.0'
|
||||
)
|
||||
(instance === instance2).should == true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,86 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
|
||||
describe WpUser do
|
||||
describe '#initialize' do
|
||||
it 'should replace nil with empty' do
|
||||
user = WpUser.new(nil, nil, nil)
|
||||
user.name.should == 'empty'
|
||||
user.id.should == 'empty'
|
||||
user.nickname == 'empty'
|
||||
end
|
||||
|
||||
it 'should initialize a user object' do
|
||||
user = WpUser.new('name', 'id', 'nickname')
|
||||
user.name.should == 'name'
|
||||
user.id.should == 'id'
|
||||
user.nickname == 'nickname'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#<=>' do
|
||||
it 'should return -1' do
|
||||
user1 = WpUser.new('b', nil, nil)
|
||||
user2 = WpUser.new('a', nil, nil)
|
||||
(user1 <=> user2).should === -1
|
||||
end
|
||||
|
||||
it 'should return 0' do
|
||||
user1 = WpUser.new('a', nil, nil)
|
||||
user2 = WpUser.new('a', nil, nil)
|
||||
(user1 <=> user2).should === 0
|
||||
end
|
||||
|
||||
it 'should return 1' do
|
||||
user1 = WpUser.new('a', nil, nil)
|
||||
user2 = WpUser.new('b', nil, nil)
|
||||
(user1 <=> user2).should === 1
|
||||
end
|
||||
end
|
||||
|
||||
describe '#===' do
|
||||
it 'should return true' do
|
||||
user1 = WpUser.new('a', 'id', 'nick')
|
||||
user2 = WpUser.new('a', 'id', 'nick')
|
||||
(user1 === user2).should be_true
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
user1 = WpUser.new('a', 'id', 'nick')
|
||||
user2 = WpUser.new('b', 'id', 'nick')
|
||||
(user1 === user2).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#eql?' do
|
||||
it 'should return true' do
|
||||
user1 = WpUser.new('a', 'id', 'nick')
|
||||
user2 = WpUser.new('a', 'id', 'nick')
|
||||
(user1.eql? user2).should be_true
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
user1 = WpUser.new('a', 'id', 'nick')
|
||||
user2 = WpUser.new('b', 'id', 'nick')
|
||||
(user1.eql? user2).should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,306 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
|
||||
describe WpVersion do
|
||||
|
||||
before :all do
|
||||
@target_uri = URI.parse('http://example.localhost/')
|
||||
@browser = Browser.instance(config_file: SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf.json')
|
||||
end
|
||||
|
||||
describe '#find_from_meta_generator' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/meta-generator' }
|
||||
|
||||
after :each do
|
||||
stub_request_to_fixture(url: @target_uri.to_s, fixture: @fixture)
|
||||
WpVersion.find_from_meta_generator(base_uri: @target_uri.to_s).should === @expected
|
||||
end
|
||||
|
||||
it 'should return nil if the meta-generator is not found' do
|
||||
@fixture = fixtures_dir + '/no-meta-generator.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return 3.3.2' do
|
||||
@fixture = fixtures_dir + '/3.3.2.htm'
|
||||
@expected = '3.3.2'
|
||||
end
|
||||
|
||||
it 'should return 3.4-beta4' do
|
||||
@fixture = fixtures_dir + '/3.4-beta4.htm'
|
||||
@expected = '3.4-beta4'
|
||||
end
|
||||
|
||||
it "should return nil if it's not a valid version, must contains at least one '.'" do
|
||||
@fixture = fixtures_dir + '/invalid_version.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return 3.5' do
|
||||
@fixture = fixtures_dir + '/3.5_minified.htm'
|
||||
@expected = '3.5'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_from_rss_generator' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/rss-generator' }
|
||||
|
||||
after :each do
|
||||
@status_code ||= 200
|
||||
stub_request_to_fixture(url: @target_uri.merge('feed/').to_s, status: @status_code, fixture: @fixture)
|
||||
WpVersion.find_from_rss_generator(base_uri: @target_uri).should === @expected
|
||||
end
|
||||
|
||||
it 'should return nil on a 404' do
|
||||
@status_code = 404
|
||||
@fixture = SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/404.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil if the rss-generator is not found' do
|
||||
@fixture = fixtures_dir + '/no-rss-generator.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil if the version is not found (but the rss-generator is present)' do
|
||||
@fixture = fixtures_dir + '/no-version.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'shuld return 3.3.2' do
|
||||
@fixture = fixtures_dir + '/3.3.2.htm'
|
||||
@expected = '3.3.2'
|
||||
end
|
||||
|
||||
it 'should return 3.4-beta4' do
|
||||
@fixture = fixtures_dir + '/3.4-beta4.htm'
|
||||
@expected = '3.4-beta4'
|
||||
end
|
||||
|
||||
it "should return nil if it's not a valid version, must contains at least one '.'" do
|
||||
@fixture = fixtures_dir + '/invalid_version.htm'
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_from_rdf_generator' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/rdf-generator' }
|
||||
|
||||
after :each do
|
||||
@status_code ||= 200
|
||||
stub_request_to_fixture(url: @target_uri.merge('feed/rdf/').to_s, status: @status_code, fixture: @fixture)
|
||||
WpVersion.find_from_rdf_generator(base_uri: @target_uri).should === @expected
|
||||
end
|
||||
|
||||
it 'should return nil on a 404' do
|
||||
@status_code = 404
|
||||
@fixture = SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/404.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil if the rdf-generator is not found' do
|
||||
@fixture = fixtures_dir + '/no-rdf-generator.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil if the version is not found (but the rdf-generator is present)' do
|
||||
@fixture = fixtures_dir + '/no-version.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'shuld return 3.3.2' do
|
||||
@fixture = fixtures_dir + '/3.3.2.htm'
|
||||
@expected = '3.3.2'
|
||||
end
|
||||
|
||||
it 'should return 3.4-beta4' do
|
||||
@fixture = fixtures_dir + '/3.4-beta4.htm'
|
||||
@expected = '3.4-beta4'
|
||||
end
|
||||
|
||||
it "should return nil if it's not a valid version, must contains at least one '.'" do
|
||||
@fixture = fixtures_dir + '/invalid_version.htm'
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_from_atom_generator' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/atom-generator' }
|
||||
|
||||
after :each do
|
||||
@status_code ||= 200
|
||||
stub_request_to_fixture(url: @target_uri.merge('feed/atom/').to_s, status: @status_code, fixture: @fixture)
|
||||
WpVersion.find_from_atom_generator(base_uri: @target_uri).should === @expected
|
||||
end
|
||||
|
||||
it 'should return nil on a 404' do
|
||||
@status_code = 404
|
||||
@fixture = SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/404.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil if the atom-generator is not found' do
|
||||
@fixture = fixtures_dir + '/no-atom-generator.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil if the version is not found (but the atom-generator is present)' do
|
||||
@fixture = fixtures_dir + '/no-version.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'shuld return 3.3.2' do
|
||||
@fixture = fixtures_dir + '/3.3.2.htm'
|
||||
@expected = '3.3.2'
|
||||
end
|
||||
|
||||
it 'should return 3.4-beta4' do
|
||||
@fixture = fixtures_dir + '/3.4-beta4.htm'
|
||||
@expected = '3.4-beta4'
|
||||
end
|
||||
|
||||
it "should return nil if it's not a valid version, must contains at least one '.'" do
|
||||
@fixture = fixtures_dir + '/invalid_version.htm'
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_from_sitemap_generator' do
|
||||
after :each do
|
||||
stub_request(:get, @target_uri.merge('sitemap.xml').to_s).
|
||||
to_return(status: 200, body: @body)
|
||||
|
||||
WpVersion.find_from_sitemap_generator(base_uri: @target_uri).should === @expected
|
||||
end
|
||||
|
||||
it 'should return nil if the generator is not found' do
|
||||
@body = ''
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return the version : 3.3.2' do
|
||||
@body = '<!-- generator="wordpress/3.3.2" -->'
|
||||
@expected = '3.3.2'
|
||||
end
|
||||
|
||||
it "should return nil if it's not a valid version, must contains at least one '.'" do
|
||||
@body = '<!-- generator="wordpress/5065" -->'
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_from_readme' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/readme' }
|
||||
|
||||
after :each do
|
||||
@status_code ||= 200
|
||||
stub_request_to_fixture(url: @target_uri.merge('readme.html').to_s, status: @status_code, fixture: @fixture)
|
||||
|
||||
WpVersion.find_from_readme(base_uri: @target_uri).should === @expected
|
||||
end
|
||||
|
||||
it 'should return nil on a 404' do
|
||||
@status_code = 404
|
||||
@fixture = SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/404.htm'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return nil if the version number is not present' do
|
||||
@fixture = fixtures_dir + '/empty-version.html'
|
||||
@expected = nil
|
||||
end
|
||||
|
||||
it 'should return 3.3.2' do
|
||||
@fixture = fixtures_dir + '/readme-3.3.2.html'
|
||||
@expected = '3.3.2'
|
||||
end
|
||||
|
||||
it "should return nil if it's not a valid version, must contains at least one '.'" do
|
||||
@fixture = fixtures_dir + '/invalid_version.html'
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_from_advanced_fingerprinting' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/advanced' }
|
||||
|
||||
it 'should return 3.2.1' do
|
||||
stub_request_to_fixture(
|
||||
url: @target_uri.merge('wp-admin/js/wp-fullscreen.js').to_s,
|
||||
fixture: "#{fixtures_dir}/3.2.1.js"
|
||||
)
|
||||
version = WpVersion.find_from_advanced_fingerprinting(
|
||||
base_uri: @target_uri,
|
||||
wp_content_dir: 'wp-content',
|
||||
version_xml: "#{fixtures_dir}/wp_versions.xml"
|
||||
)
|
||||
version.should == '3.2.1'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_from_links_opml' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/opml' }
|
||||
|
||||
it 'should return 3.4.2' do
|
||||
stub_request_to_fixture(
|
||||
url: @target_uri.merge('wp-links-opml.php').to_s,
|
||||
fixture: "#{fixtures_dir}/wp-links-opml.xml"
|
||||
)
|
||||
version = WpVersion.find_from_links_opml(base_uri: @target_uri)
|
||||
version.should == '3.4.2'
|
||||
end
|
||||
|
||||
it 'should return nil' do
|
||||
stub_request_to_fixture(
|
||||
url: @target_uri.merge('wp-links-opml.php').to_s,
|
||||
fixture: "#{fixtures_dir}/wp-links-opml-nogenerator.xml"
|
||||
)
|
||||
version = WpVersion.find_from_links_opml(base_uri: @target_uri)
|
||||
version.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'should initialize a WpVersion object' do
|
||||
v = WpVersion.new(1, {discovery_method: 'method', vulns_file: 'asdf.xml'})
|
||||
v.number.should == 1
|
||||
v.discovery_method.should == 'method'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find' do
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR + '/advanced' }
|
||||
|
||||
it 'should find all versions' do
|
||||
# All requests get a HTTP 404
|
||||
stub_request(:any, /.*/).to_return(status: 404)
|
||||
# Wordpress Version 3.2.1
|
||||
stub_request_to_fixture(
|
||||
url: @target_uri.merge('wp-admin/js/wp-fullscreen.js').to_s,
|
||||
fixture: "#{fixtures_dir}/3.2.1.js"
|
||||
)
|
||||
version = WpVersion.find(@target_uri, 'wp-content')
|
||||
version.number.should == '3.2.1'
|
||||
version.discovery_method.should == 'advanced fingerprinting'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,37 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
# TODO
|
||||
|
||||
describe '#vulnerabilities' do
|
||||
let(:location_url) { 'http://example.localhost/' }
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_PLUGIN_DIR + '/vulnerabilities' }
|
||||
let(:vulns_file) { fixtures_dir + '/plugin_vulns.xml' }
|
||||
let(:wp_plugin) do
|
||||
WpPlugin.new(
|
||||
base_url: location_url,
|
||||
name: 'spec-plugin',
|
||||
path: 'plugins/spec-plugin/',
|
||||
vulns_file: vulns_file
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
it 'should return an empty array when no vulnerabilities are found' do
|
||||
WpPlugin.new(
|
||||
base_url: 'http://example.localhost/',
|
||||
name: 'no-vulns',
|
||||
path: 'plugins/no-vulns/',
|
||||
vulns_file: vulns_file
|
||||
).vulnerabilities.should be_empty
|
||||
end
|
||||
|
||||
it 'should return an arry with 2 vulnerabilities' do
|
||||
vulnerabilities = wp_plugin.vulnerabilities
|
||||
|
||||
vulnerabilities.should_not be_empty
|
||||
vulnerabilities.length.should == 2
|
||||
vulnerabilities.each { |vulnerability| vulnerability.should be_a WpVulnerability }
|
||||
vulnerabilities[0].title.should === 'WPScan Spec'
|
||||
vulnerabilities[1].title.should === 'Spec SQL Injection'
|
||||
end
|
||||
end
|
||||
@@ -54,17 +54,15 @@ describe 'StatsPlugin' do
|
||||
|
||||
describe '#total_plugins' do
|
||||
it 'should return the correct numer' do
|
||||
xml = "#{SPEC_FIXTURES_WPSCAN_WP_PLUGIN_DIR}/vulnerabilities/plugin_vulns.xml"
|
||||
file = "#{SPEC_FIXTURES_WPSCAN_WP_PLUGIN_DIR}/plugins.txt"
|
||||
@stats.total_plugins(file, xml).should == 4
|
||||
@stats.total_plugins(file).should == 4
|
||||
end
|
||||
end
|
||||
|
||||
describe '#total_themes' do
|
||||
it 'should return the correct numer' do
|
||||
xml = "#{SPEC_FIXTURES_WPSCAN_WP_THEME_DIR}/vulnerabilities/theme_vulns.xml"
|
||||
file = "#{SPEC_FIXTURES_WPSCAN_WP_THEME_DIR}/themes.txt"
|
||||
@stats.total_themes(file, xml).should == 5
|
||||
@stats.total_themes(file).should == 5
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user