Merge branch 'new-enumeration-system'

This commit is contained in:
erwanlr
2013-04-05 14:07:06 +02:00
292 changed files with 6555 additions and 26491 deletions

View File

@@ -1,21 +1,4 @@
# 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 'common/typhoeus_cache'

View File

@@ -1,21 +1,4 @@
# 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/>.
#++
#
# => @todo take consideration of the cache_timeout :

View File

@@ -0,0 +1,8 @@
# encoding: UTF-8
require 'common/collections/vulnerabilities/output'
class Vulnerabilities < Array
include Vulnerabilities::Output
end

View 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

View File

@@ -0,0 +1,9 @@
# 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

View File

@@ -0,0 +1,186 @@
# encoding: UTF-8
class WpItems < Array
module Detectable
attr_reader :vulns_file, :item_xpath
# @param [ Wptarget ] wp_target
# @param [ Hash ] options
# @option options [ Boolean ] :show_progression Whether or not output the progress bar
# @option options [ Boolean ] :only_vulnerable Only check for vulnerable items
# @option options [ String ] :exclude_content
#
# @return [ WpItems ]
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
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
#
# @return [ WpItems ]
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: self.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
# The default request parameters
#
# @return [ Hash ]
def request_params; { cache_ttl: 0, followlocation: true } end
# @param [ WpTarget ] wp_target
# @param [ options ] options
# @option options [ Boolean ] :only_vulnerable
# @option options [ String ] :file The path to the file containing the targets
#
# @return [ Array<WpItem> ]
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
# @param [ WpTarget ] wp_target
# @param [ Class ] item_class
# @param [ String ] vulns_file
#
# @return [ Array<WpItem> ]
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
# @param [ Class ] klass
# @param [ String ] name
# @param [ WpTarget ] wp_target
# @option [ String ] vulns_file
#
# @return [ WpItem ]
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
# @param [ String ] file
# @param [ WpTarget ] wp_target
# @param [ Class ] item_class
# @param [ String ] vulns_file
#
# @return [ WpItem ]
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

View File

@@ -0,0 +1,11 @@
# encoding: UTF-8
class WpItems < Array
module Output
def output
self.each { |item| item.output }
end
end
end

View File

@@ -0,0 +1,8 @@
# encoding: UTF-8
require 'common/collections/wp_plugins/detectable'
class WpPlugins < WpItems
extend WpPlugins::Detectable
end

View File

@@ -0,0 +1,17 @@
# encoding: UTF-8
class WpPlugins < WpItems
module Detectable
# @return [ String ]
def vulns_file
PLUGINS_VULNS_FILE
end
# @return [ String ]
def item_xpath
'//plugin'
end
end
end

View File

@@ -0,0 +1,8 @@
# encoding: UTF-8
require 'common/collections/wp_themes/detectable'
class WpThemes < WpItems
extend WpThemes::Detectable
end

View File

@@ -0,0 +1,17 @@
# encoding: UTF-8
class WpThemes < WpItems
module Detectable
# @return [ String ]
def vulns_file
THEMES_VULNS_FILE
end
# @return [ String ]
def item_xpath
'//theme'
end
end
end

View File

@@ -0,0 +1,8 @@
# encoding: UTF-8
require 'common/collections/wp_timthumbs/detectable'
class WpTimthumbs < WpItems
extend WpTimthumbs::Detectable
end

View File

@@ -0,0 +1,83 @@
# encoding: UTF-8
class WpTimthumbs < WpItems
module Detectable
# No passive detection
#
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
#
# @return [ WpTimthumbs ]
def passive_detection(wp_target, options = {})
new
end
protected
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
# @option options [ String ] :file The path to the file containing the targets
# @option options [ String ] :theme_name
#
# @return [ Array<WpTimthumb> ]
def targets_items(wp_target, options = {})
targets = options[:theme_name] ? theme_timthumbs(options[:theme_name], wp_target) : []
if options[:file]
targets += targets_items_from_file(options[:file], wp_target)
end
targets.uniq { |i| i.url }
end
# @param [ String ] theme_name
# @param [ WpTarget ] wp_target
#
# @return [ Array<WpTimthumb> ]
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
# @param [ String ] file
# @param [ WpTarget ] wp_target
#
# @return [ Array<WpTimthumb> ]
def targets_items_from_file(file, wp_target)
targets = []
File.open(file, 'r') do |f|
f.readlines.collect do |path|
targets << create_item(wp_target, path.strip)
end
end
targets
end
# @param [ WpTarget ] wp_target
# @option [ String ] path
#
# @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

View 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

View File

@@ -0,0 +1,34 @@
# encoding: UTF-8
class WpUsers < WpItems
module Detectable
# @return [ Hash ]
def request_params; {} end
# No passive detection
#
# @return [ WpUsers ]
def passive_detection(wp_target, options = {})
new
end
protected
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
# @option options [ Range ] :range ((1..10))
#
# @return [ Array<WpUser> ]
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
end
end

View 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

View File

@@ -1,21 +1,4 @@
# 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/>.
#++
LIB_DIR = File.expand_path(File.dirname(__FILE__) + '/..')
ROOT_DIR = File.expand_path(LIB_DIR + '/..') # expand_path is used to get "wpscan/" instead of "wpscan/lib/../"
@@ -26,7 +9,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 +36,8 @@ LOCAL_FILES_XSD = DATA_DIR + '/local_vulnerable_files.xsd'
WPSCAN_VERSION = '2.1'
$LOAD_PATH.unshift(LIB_DIR)
$LOAD_PATH.unshift(WPSCAN_LIB_DIR)
$LOAD_PATH.unshift(MODELS_LIB_DIR)
require 'environment'
@@ -75,31 +64,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 +102,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

View File

@@ -1,21 +1,4 @@
# 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 CustomOptionParser < OptionParser

View File

@@ -4,9 +4,13 @@
# See http://rosettacode.org/wiki/URL_encoding#Ruby and http://www.ruby-forum.com/topic/207489
if RUBY_VERSION >= '1.9.2'
module URI
def self.escape(str)
URI.encode_www_form_component(str).gsub('+', '%20')
extend self
def escape(str)
URI::Parser.new.escape(str)
end
alias :encode :escape
end
end
@@ -26,6 +30,23 @@ if RUBY_VERSION < '1.9'
end
end
# This is used in WpItem::Existable
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
# Override for puts to enable logging
def puts(o = '')
# remove color for logging

View File

@@ -0,0 +1,47 @@
# encoding: UTF-8
require 'vulnerability/output'
class Vulnerability
include Vulnerability::Output
attr_accessor :title, :references, :type, :metasploit_modules
#
# @param [ String ] title The title of the vulnerability
# @param [ String ] type The type of the vulnerability
# @param [ Array ] references References urls
# @param [ Array ] metasploit_modules Metasploit modules for the vulnerability
#
# @return [ Vulnerability ]
def initialize(title, type, references, metasploit_modules = [])
@title = title
@type = type
@references = references
@metasploit_modules = metasploit_modules
end
# @param [ Vulnerability ] other
#
# @return [ Boolean ]
# :nocov:
def ==(other)
title == other.title && type == other.type && references == other.references
end
# :nocov:
# Create the Vulnerability from the xml_node
#
# @param [ Nokogiri::XML::Node ] xml_node
#
# @return [ Vulnerability ]
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

View File

@@ -0,0 +1,26 @@
# 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
# @return [ String ] The url to the metasploit module page
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

103
lib/common/models/wp_item.rb Executable file
View File

@@ -0,0 +1,103 @@
# 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
# @return [ Array ]
# Make it private ?
def allowed_options
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :vulns_file]
end
# @param [ URI ] target_base_uri
# @param [ Hash ] options See allowed_option
#
# @return [ WpItem ]
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
# @param [ Hash ] options
#
# @return [ void ]
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
# @param [ URI ] target_base_uri
#
# @return [ void ]
def forge_uri(target_base_uri)
@uri = target_base_uri
end
# @return [ URI ] The uri to the WpItem, with the path if present
def uri
path ? @uri.merge(path) : @uri
end
# @return [ String ] The url to the WpItem
def url; uri.to_s end
# Sets the path
#
# Variable, such as $wp-plugins$ and $wp-content$ can be used
# and will be replace by their value
#
# @param [ String ] path
#
# @return [ void ]
def path=(path)
@path = URI.encode(
path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
)
end
# @param [ WpItem ] other
def <=>(other)
name <=> other.name
end
# @param [ WpItem ] other
def ==(other)
name === other.name
end
# @param [ WpItem ] other
def ===(other)
self == other && version === other.version
end
end

View File

@@ -0,0 +1,50 @@
# encoding: UTF-8
class WpItem
module Existable
# Check the existence of the WpItem
# If the response is supplied, it's used for the verification
# Otherwise a new request is done
#
# @param [ Hash ] options See exists_from_response?
# @param [ Typhoeus::Response ] response
#
# @return [ Boolean ]
def exists?(options = {}, response = nil)
unless response
response = Browser.instance.get(url)
end
exists_from_response?(response, options)
end
protected
# @param [ Typhoeus::Response ] response
# @param [ options ] options
#
# @option options [ Hash ] :error_404_hash The hash of the error 404 page
# @option options [ Hash ] :homepage_hash The hash of the homepage
# @option options [ Hash ] :exclude_content A regexp with the pattern to exclude from the body of the response
#
# @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
# See https://github.com/typhoeus/typhoeus/issues/279
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

View File

@@ -0,0 +1,19 @@
# encoding: UTF-8
class WpItem
attr_reader :found_from
# Sets the found_from attribute
#
# @param [ String ] method The method which found the WpItem
#
# @return [ void ]
def found_from=(method)
found = method[%r{find_from_(.*)}, 1]
@found_from = found.gsub('_', ' ') if found
end
module Findable
end
end

View File

@@ -0,0 +1,55 @@
# encoding: UTF-8
class WpItem
# @uri is used instead of #uri to avoid the presence of the :path into it
module Infos
# @return [ Boolean ]
def has_readme?
Browser.instance.get(readme_url).code == 200 ? true : false
end
# @return [ String ] The url to the readme file
def readme_url
@uri.merge('readme.txt').to_s
end
# @return [ Boolean ]
def has_changelog?
Browser.instance.get(changelog_url).code == 200 ? true : false
end
# @return [ String ] The url to the changelog file
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/
#
# Only the first 700 bytes are checked to avoid the download
# of the whole file which can be very huge (like 2 Go)
#
# @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 ] The url to the error_log file
def error_log_url
@uri.merge('error_log').to_s
end
end
end

View 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

View File

@@ -0,0 +1,26 @@
# encoding: UTF-8
class WpItem
attr_writer :version
module Versionable
# Get the version from the readme.txt
#
# @return [ String ] The version number
def version
unless @version
response = Browser.instance.get(readme_url)
@version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1]
end
@version
end
# @return [ String ]
def to_s
item_version = self.version
"#@name#{' v' + item_version.strip if item_version}"
end
end
end

View File

@@ -0,0 +1,21 @@
# encoding: UTF-8
class WpItem
module Vulnerable
attr_accessor :vulns_file, :vulns_xpath
# Get the vulnerabilities associated to the WpItem
#
# @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

17
lib/common/models/wp_plugin.rb Executable file
View File

@@ -0,0 +1,17 @@
# encoding: UTF-8
require 'wp_plugin/vulnerable'
class WpPlugin < WpItem
include WpPlugin::Vulnerable
# Sets the @uri
#
# @param [ URI ] target_base_uri The URI of the wordpress blog
#
# @return [ void ]
def forge_uri(target_base_uri)
@uri = target_base_uri.merge(URI.encode(wp_plugins_dir + '/' + name + '/'))
end
end

View File

@@ -0,0 +1,20 @@
# encoding: UTF-8
class WpPlugin < WpItem
module Vulnerable
# @return [ String ] The path to the file containing vulnerabilities
def vulns_file
unless @vulns_file
@vulns_file = PLUGINS_VULNS_FILE
end
@vulns_file
end
# @return [ String ]
def vulns_xpath
"//plugin[@name='#{@name}']/vulnerability"
end
end
end

33
lib/common/models/wp_theme.rb Executable file
View File

@@ -0,0 +1,33 @@
# encoding: UTF-8
require 'wp_theme/findable'
require 'wp_theme/versionable'
require 'wp_theme/vulnerable'
class WpTheme < WpItem
extend WpTheme::Findable
include WpTheme::Versionable
include WpTheme::Vulnerable
attr_writer :style_url
def allowed_options; super << :style_url end
# Sets the @uri
#
# @param [ URI ] target_base_uri The URI of the wordpress blog
#
# @return [ void ]
def forge_uri(target_base_uri)
@uri = target_base_uri.merge(URI.encode(wp_content_dir + '/themes/' + name + '/'))
end
# @return [ String ] The url to the theme stylesheet
def style_url
unless @style_url
@style_url = uri.merge('style.css').to_s
end
@style_url
end
end

View File

@@ -0,0 +1,72 @@
# encoding: UTF-8
class WpTheme < WpItem
module Findable
# Find the main theme of the blog
#
# @param [ URI ] target_uri
#
# @return [ WpTheme ]
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
nil
end
protected
# Discover the wordpress theme by parsing the css link rel
#
# @param [ URI ] target_uri
#
# @return [ WpTheme ]
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
#
# @param [ URI ] target_uri
#
# @return [ WpTheme ]
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?([^"]+)?" />}
if matches = regexp.match(body)
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
}
)
end
end
end
end

View File

@@ -0,0 +1,17 @@
# 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
@version ||= super
end
@version
end
end
end

View File

@@ -0,0 +1,20 @@
# encoding: UTF-8
class WpTheme < WpItem
module Vulnerable
# @return [ String ] The path to the file containing vulnerabilities
def vulns_file
unless @vulns_file
@vulns_file = THEMES_VULNS_FILE
end
@vulns_file
end
# @return [ String ]
def vulns_xpath
"//theme[@name='#{@name}']/vulnerability"
end
end
end

View File

@@ -0,0 +1,18 @@
# 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
# @param [ WpTimthumb ] other
#
# @return [ Boolean ]
def ==(other)
url == other.url
end
end

View File

@@ -0,0 +1,15 @@
# encoding: UTF-8
class WpTimthumb < WpItem
module Existable
# @param [ Typhoeus::Response ] response
# @param [ Hash ] options
#
# @return [ Boolean ]
def exists_from_response?(response, options = {})
response.code == 400 && response.body =~ /no image specified/i ? true : false
end
end
end

View File

@@ -0,0 +1,11 @@
# encoding: UTF-8
class WpTimthumb < WpItem
module Output
def output
puts ' | ' + red('[!]') + " #{self}"
end
end
end

View File

@@ -0,0 +1,24 @@
# 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
#
# @return [ String ] The version
def version
unless @version
response = Browser.instance.get(url)
@version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1]
end
@version
end
# @return [ String ]
def to_s
"#{url}#{ ' v' + version if version}"
end
end
end

49
lib/common/models/wp_user.rb Executable file
View File

@@ -0,0 +1,49 @@
# encoding: UTF-8
require 'wp_user/existable'
class WpUser < WpItem
include WpUser::Existable
attr_accessor :id, :login, :display_name, :password
# @return [ Array<Symbol> ]
def allowed_options; [:id, :login, :display_name, :password] end
# @return [ URI ] The uri to the auhor page
def uri
if id
return @uri.merge("?author=#{id}")
else
raise 'The id is nil'
end
end
# @return [ String ]
def to_s
s = "#{id}"
s += " | #{login}" if login
s += " | #{display_name}" if display_name
s
end
# @param [ WpUser ] other
def <=>(other)
id <=> other.id
end
# @param [ WpUser ] other
#
# @return [ Boolean ]
def ==(other)
self === other
end
# @param [ WpUser ] other
#
# @return [ Boolean ]
def ===(other)
id === other.id && login === other.login
end
end

View File

@@ -0,0 +1,78 @@
# encoding: UTF-8
class WpUser < WpItem
module Existable
# @param [ Typhoeus::Response ] response
# @param [ Hash ] options
#
# @return [ Boolean ]
def exists_from_response?(response, options = {})
load_from_response(response)
@login ? true : false
end
# Load the login and display_name from the response
#
# @param [ Typhoeus::Response ] response
#
# @return [ void ]
def load_from_response(response)
if response.code == 301 # login in location?
location = response.headers_hash['Location']
@login = Existable.login_from_author_pattern(location)
@display_name = Existable.display_name_from_body(
Browser.instance.get(location).body
)
elsif response.code == 200 # login in body?
@login = Existable.login_from_body(response.body)
@display_name = Existable.display_name_from_body(response.body)
end
end
private :load_from_response
# @param [ String ] text
#
# @return [ String ] The login
def self.login_from_author_pattern(text)
text[%r{/author/([^/\b]+)/?}i, 1]
end
# @param [ String ] body
#
# @return [ String ] The login
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
# @note Some bodies are encoded in ASCII-8BIT, and Nokogiri doesn't support it
# So it's forced to UTF-8 when this encoding is detected
#
# @param [ String ] body
#
# @return [ String ] The display_name
def self.display_name_from_body(body)
if title_tag = body[%r{<title>([^<]+)</title>}i, 1]
title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT
title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s
# &amp; are not decoded with Nokogiri
title_tag.sub!('&amp;', '&')
name = title_tag[%r{([^|«]+) }, 1]
return name.strip if name
end
end
end
end

26
lib/common/models/wp_version.rb Executable file
View File

@@ -0,0 +1,26 @@
# 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
# The version number
attr_accessor :number
# @return [ Array ]
def allowed_options; super << :number << :found_from end
# @param [ WpVersion ] other
#
# @return [ Boolean ]
def ==(other)
number == other.number
end
end

View File

@@ -0,0 +1,220 @@
# encoding: UTF-8
class WpVersion < WpItem
module Findable
# Find the version of the blog designated from target_uri
#
# @param [ URI ] target_uri
# @param [ String ] wp_content_dir
# @param [ String ] wp_plugins_dir
#
# @return [ WpVersion ]
def find(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
methods.grep(/find_from_/).each do |method|
if method === :find_from_advanced_fingerprinting
version = send(method, target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
else
version = send(method, target_uri)
end
if version
return new(target_uri, number: version, found_from: method)
end
end
nil
end
# Used to check if the version is correct: must contain at least one dot.
#
# @return [ String ]
def version_pattern
'([^\r\n"\']+\.[^\r\n"\']+)'
end
protected
# Returns the first match of <pattern> in the body of the url
#
# @param [ URI ] target_uri
# @param [ Regex ] pattern
# @param [ String ] path
#
# @return [ String ]
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.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_meta_generator(target_uri)
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.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_rss_generator(target_uri)
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.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_rdf_generator(target_uri)
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)
# 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.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_atom_generator(target_uri)
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)
# 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)
#
# @param [ URI ] target_uri
# @param [ String ] wp_content_dir
# @param [ String ] wp_plugins_dir
# @param [ String ] versions_xml The path to the xml containing all versions
#
# @return [ String ] The version number
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
xml = xml(versions_xml)
# This wp_item will take care of encoding the path
# and replace variables like $wp-content$ & $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.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_readme(target_uri)
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
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_sitemap_generator(target_uri)
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.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_links_opml(target_uri)
scan_url(
target_uri,
%r{generator="wordpress/#{version_pattern}"}i,
'wp-links-opml.php'
)
end
end
end

View 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

View File

@@ -0,0 +1,20 @@
# encoding: UTF-8
class WpVersion < WpItem
module Vulnerable
# @return [ String ] The path to the file containing vulnerabilities
def vulns_file
unless @vulns_file
@vulns_file = WP_VULNS_FILE
end
@vulns_file
end
# @return [ String ]
def vulns_xpath
"//wordpress[@version='#{@number}']/vulnerability"
end
end
end

View File

@@ -1,21 +1,4 @@
# 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 Plugin

View File

@@ -1,21 +1,4 @@
# 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 Plugins < Array

View File

@@ -1,21 +1,4 @@
# 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 'common/cache_file_store'

View File

@@ -1,21 +1,4 @@
# 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 'common/updater/updater'

View File

@@ -1,21 +1,4 @@
# 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 'common/updater/updater'

View File

@@ -1,21 +1,4 @@
# 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/>.
#++
# This class act as an absract one
class Updater

View File

@@ -1,21 +1,4 @@
# 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 UpdaterFactory

View File

@@ -1,21 +1,4 @@
# 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 'rubygems'
@@ -27,7 +10,6 @@ end
begin
# Standard libs
require 'rubygems'
require 'bundler/setup'
require 'getoptlong'
require 'optparse' # Will replace getoptlong

View File

@@ -1,36 +1,19 @@
# 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 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 +29,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 +44,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 +69,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.

View File

@@ -1,21 +1,4 @@
# 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 Malwares
# Used as cache :

View File

@@ -1,21 +1,4 @@
# 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 WpConfigBackup

View File

@@ -1,21 +1,4 @@
# 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 WpFullPathDisclosure

View File

@@ -1,21 +1,4 @@
# 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 WpLoginProtection
@@ -38,10 +21,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 +37,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 +78,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

View File

@@ -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

View File

@@ -1,21 +1,4 @@
# 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 WpReadme

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,21 +1,4 @@
# 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 WebSite

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,21 +1,12 @@
# 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 'web_site'
require 'modules/wp_readme'
require 'modules/wp_full_path_disclosure'
require 'modules/wp_config_backup'
require 'modules/wp_login_protection'
require 'modules/malwares'
require 'modules/brute_force'
class WpTarget < WebSite
include WpReadme
@@ -23,10 +14,6 @@ class WpTarget < WebSite
include WpConfigBackup
include WpLoginProtection
include Malwares
include WpUsernames
include WpTimthumbs
include WpPlugins
include WpThemes
include BruteForce
attr_reader :verbose
@@ -90,9 +77,21 @@ class WpTarget < WebSite
WpTheme.find(@uri)
end
# return WpVersion
def version
WpVersion.find(@uri, wp_content_dir)
# @param [ String ] versions_xml
#
# @return [ WpVersion ]
def version(versions_xml)
WpVersion.find(@uri, wp_content_dir, wp_plugins_dir, versions_xml)
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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,21 +1,4 @@
# 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__) + '/../common/common_helper')

View File

@@ -1,21 +1,4 @@
# 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 WpscanOptions

View File

@@ -1,21 +1,4 @@
# 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 CheckerPlugin < Plugin

View File

@@ -1,21 +1,4 @@
# 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/>.
#++
# This tool generates a list to use for plugin and theme enumeration
class GenerateList

View File

@@ -1,21 +1,4 @@
# 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 ListGeneratorPlugin < Plugin

View File

@@ -1,21 +1,4 @@
# 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/>.
#++
# This Class Parses SVN Repositories via HTTP
class SvnParser

View File

@@ -1,24 +1,4 @@
# 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/wp_enumerator'
require 'wpscan/wp_item'
class StatsPlugin < Plugin
@@ -60,23 +40,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

View File

@@ -1,21 +1,4 @@
# 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__) + '/../common/common_helper')