First commit for more generic enumerating and scanning
This commit is contained in:
66
lib/wpscan/modules/wp_item.rb
Normal file
66
lib/wpscan/modules/wp_item.rb
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#--
|
||||||
|
# WPScan - WordPress Security Scanner
|
||||||
|
# Copyright (C) 2012
|
||||||
|
#
|
||||||
|
# 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 WpItem
|
||||||
|
attr_accessor :path, :base_url, :wp_content_dir
|
||||||
|
@version = nil
|
||||||
|
|
||||||
|
def get_url
|
||||||
|
URI.parse("#{@base_url.to_s}#@wp_content_dir/#@path")
|
||||||
|
end
|
||||||
|
|
||||||
|
def version
|
||||||
|
unless @version
|
||||||
|
response = Browser.instance.get(get_url.merge("readme.txt").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(location_uri_from_file_url(get_url.to_s)).body[%r{<title>Index of}] ? true : false
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_name_from_url(url)
|
||||||
|
url.to_s[%r{^(https?://.*/([^/]+)/)}i, 2]
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
item_version = version
|
||||||
|
"#@name#{' v' + item_version if item_version}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def ==(item)
|
||||||
|
item.name == @name
|
||||||
|
end
|
||||||
|
|
||||||
|
def <=>(item)
|
||||||
|
item.name <=> @name
|
||||||
|
end
|
||||||
|
|
||||||
|
def location_uri_from_file_url(location_url)
|
||||||
|
valid_location_url = location_url[%r{^(https?://.*/)[^.]+\.[^/]+$}, 1]
|
||||||
|
unless valid_location_url
|
||||||
|
valid_location_url = add_trailing_slash(location_url)
|
||||||
|
end
|
||||||
|
URI.parse(valid_location_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -37,11 +37,8 @@ module WpLoginProtection
|
|||||||
plugin_name = symbol_to_call[@@login_protection_method_pattern, 1].gsub('_', '-')
|
plugin_name = symbol_to_call[@@login_protection_method_pattern, 1].gsub('_', '-')
|
||||||
|
|
||||||
return @login_protection_plugin = WpPlugin.new(
|
return @login_protection_plugin = WpPlugin.new(
|
||||||
WpPlugin::create_location_url_from_name(
|
:name => plugin_name,
|
||||||
plugin_name,
|
:base_url => @uri.to_s
|
||||||
@uri.to_s
|
|
||||||
),
|
|
||||||
:name => plugin_name
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,92 +19,18 @@
|
|||||||
module WpPlugins
|
module WpPlugins
|
||||||
|
|
||||||
# Enumerate installed plugins.
|
# Enumerate installed plugins.
|
||||||
# Available options : see #targets_url
|
|
||||||
# :show_progress_bar - default false
|
|
||||||
#
|
#
|
||||||
# return array of WpPlugin
|
# return array of WpPlugin
|
||||||
def plugins_from_aggressive_detection(options = {})
|
def plugins_from_aggressive_detection(options)
|
||||||
browser = Browser.instance
|
options[:file] = "#{DATA_DIR}/plugins.txt"
|
||||||
hydra = browser.hydra
|
options[:vulns_file] = "#{DATA_DIR}/plugin_vulns.xml"
|
||||||
found_plugins = options[:only_vulnerable_ones] ? [] : plugins_from_passive_detection()
|
options[:vulns_xpath] = "//plugin[@name='#{@name}']/vulnerability"
|
||||||
request_count = 0
|
options[:type] = "plugins"
|
||||||
queue_count = 0
|
result = WpDetector.aggressive_detection(options)
|
||||||
local_404_hash = error_404_hash()
|
result
|
||||||
valid_response_codes = WpPlugins.valid_response_codes()
|
|
||||||
targets_url = plugins_targets_url(options)
|
|
||||||
show_progress_bar = options[:show_progress_bar] || false
|
|
||||||
|
|
||||||
targets_url.each do |target_url|
|
|
||||||
request = browser.forge_request(target_url, :cache_timeout => 0, :follow_location => true)
|
|
||||||
request_count += 1
|
|
||||||
|
|
||||||
request.on_complete do |response|
|
|
||||||
print "\rChecking for #{targets_url.size} total plugins... #{(request_count * 100) / targets_url.size}% complete." if show_progress_bar
|
|
||||||
|
|
||||||
if valid_response_codes.include?(response.code)
|
|
||||||
if Digest::MD5.hexdigest(response.body) != local_404_hash
|
|
||||||
found_plugins << WpPlugin.new(target_url)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
hydra.queue(request)
|
private
|
||||||
queue_count += 1
|
|
||||||
|
|
||||||
if queue_count == browser.max_threads
|
|
||||||
hydra.run
|
|
||||||
queue_count = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
hydra.run
|
|
||||||
|
|
||||||
found_plugins
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.valid_response_codes
|
|
||||||
[200, 403, 301, 302]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Available options :
|
|
||||||
# :only_vulnerable_ones - default false
|
|
||||||
# :plugins_file - default DATA_DIR/plugins.txt
|
|
||||||
# :plugin_vulns_file - default DATA_DIR/plugin_vulns.xml
|
|
||||||
#
|
|
||||||
# @return Array of String
|
|
||||||
def plugins_targets_url(options = {})
|
|
||||||
only_vulnerable = options[:only_vulnerable_ones] || false
|
|
||||||
plugins_file = options[:plugins_file] || "#{DATA_DIR}/plugins.txt"
|
|
||||||
plugin_vulns_file = options[:plugin_vulns_file] || "#{DATA_DIR}/plugin_vulns.xml"
|
|
||||||
targets_url = []
|
|
||||||
|
|
||||||
if only_vulnerable == false
|
|
||||||
# Open and parse the 'most popular' plugin list...
|
|
||||||
File.open(plugins_file, 'r') do |file|
|
|
||||||
file.readlines.collect do |line|
|
|
||||||
targets_url << WpPlugin.create_url_from_raw(line.chomp, @uri)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
xml = Nokogiri::XML(File.open(plugin_vulns_file)) do |config|
|
|
||||||
config.noblanks
|
|
||||||
end
|
|
||||||
|
|
||||||
# We check if the plugin name from the plugin_vulns_file is already in targets, otherwise we add it
|
|
||||||
xml.xpath("//plugin").each do |node|
|
|
||||||
plugin_name = node.attribute('name').text
|
|
||||||
|
|
||||||
if targets_url.grep(%r{/#{plugin_name}/}).empty?
|
|
||||||
targets_url << WpPlugin.create_location_url_from_name(plugin_name, url())
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
targets_url.flatten!
|
|
||||||
targets_url.uniq!
|
|
||||||
# randomize the plugins array to *maybe* help in some crappy IDS/IPS/WAF detection
|
|
||||||
targets_url.sort_by! { rand }
|
|
||||||
end
|
|
||||||
|
|
||||||
# http://code.google.com/p/wpscan/issues/detail?id=42
|
# http://code.google.com/p/wpscan/issues/detail?id=42
|
||||||
# plugins can be found in the source code :
|
# plugins can be found in the source code :
|
||||||
@@ -112,18 +38,16 @@ module WpPlugins
|
|||||||
# <link rel='stylesheet' href='http://example.com/wp-content/plugins/wp-minify/..' type='text/css' media='screen'/>
|
# <link rel='stylesheet' href='http://example.com/wp-content/plugins/wp-minify/..' type='text/css' media='screen'/>
|
||||||
# ...
|
# ...
|
||||||
# return array of WpPlugin
|
# return array of WpPlugin
|
||||||
def plugins_from_passive_detection
|
def plugins_from_passive_detection(wp_content_dir)
|
||||||
plugins = []
|
plugins = []
|
||||||
response = Browser.instance.get(url())
|
temp = WpDetector.passive_detection(url(), "plugins", wp_content_dir)
|
||||||
plugins_names = response.body.scan(%r{(?:[^=:]+)\s?(?:=|:)\s?(?:"|')[^"']+\\?/wp-content\\?/plugins\\?/([^/\\"']+)\\?(?:/|"|')}i)
|
|
||||||
|
|
||||||
plugins_names.flatten!
|
temp.each do |item|
|
||||||
plugins_names.uniq!
|
|
||||||
|
|
||||||
plugins_names.each do |plugin_name|
|
|
||||||
plugins << WpPlugin.new(
|
plugins << WpPlugin.new(
|
||||||
WpPlugin.create_location_url_from_name(plugin_name, url()),
|
:base_url => item[:base_url],
|
||||||
:name => plugin_name
|
:name => item[:name],
|
||||||
|
:path => item[:path],
|
||||||
|
:wp_content_dir => wp_content_dir
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
plugins
|
plugins
|
||||||
|
|||||||
57
lib/wpscan/wp_detector.rb
Normal file
57
lib/wpscan/wp_detector.rb
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#--
|
||||||
|
# WPScan - WordPress Security Scanner
|
||||||
|
# Copyright (C) 2012
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
unless items == nil or items.length == 0
|
||||||
|
result = passive_detection(options[:url], options[:type], options[:wp_content_dir])
|
||||||
|
end
|
||||||
|
|
||||||
|
enum_results = WpEnumerator.enumerate(options)
|
||||||
|
enum_results.each do |enum_result|
|
||||||
|
result << enum_result
|
||||||
|
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}#{wp_content_dir}#{regex2}#{type}#{regex3}/i)
|
||||||
|
|
||||||
|
names.flatten!
|
||||||
|
names.uniq!
|
||||||
|
|
||||||
|
names.each do |item|
|
||||||
|
items << { :base_url => url, :name => item, :path => "#{type}/#{item}" }
|
||||||
|
end
|
||||||
|
items
|
||||||
|
end
|
||||||
|
end
|
||||||
118
lib/wpscan/wp_enumerator.rb
Normal file
118
lib/wpscan/wp_enumerator.rb
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#--
|
||||||
|
# WPScan - WordPress Security Scanner
|
||||||
|
# Copyright (C) 2012
|
||||||
|
#
|
||||||
|
# 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_progress_bar+ - Show a progress bar during enumeration
|
||||||
|
def self.enumerate(options = {})
|
||||||
|
|
||||||
|
WpOptions.check_options(options)
|
||||||
|
|
||||||
|
targets = self.generate_items(options)
|
||||||
|
|
||||||
|
found = []
|
||||||
|
queue_count = 0
|
||||||
|
request_count = 0
|
||||||
|
enum_browser = Browser.instance
|
||||||
|
enum_hydra = enum_browser.hydra
|
||||||
|
enumerate_size = targets.size
|
||||||
|
|
||||||
|
targets.each do |target|
|
||||||
|
url = target.get_url
|
||||||
|
request = enum_browser.forge_request(url, :cache_timeout => 0, :follow_location => true)
|
||||||
|
request_count += 1
|
||||||
|
|
||||||
|
request.on_complete do |response|
|
||||||
|
if options[:show_progress_bar]
|
||||||
|
print "\rChecking for #{enumerate_size} total #{options[:type]}... #{(request_count * 100) / enumerate_size}% complete."
|
||||||
|
end
|
||||||
|
if WpTarget.valid_response_codes.include?(response.code)
|
||||||
|
if Digest::MD5.hexdigest(response.body) != options[:error_404_hash]
|
||||||
|
found << target
|
||||||
|
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
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def self.generate_items(options = {})
|
||||||
|
only_vulnerable = options[:only_vulnerable_ones]
|
||||||
|
plugins_file = options[:file] || "#{DATA_DIR}/plugins.txt"
|
||||||
|
plugin_vulns_file = options[:vulns_file] || "#{DATA_DIR}/plugin_vulns.xml"
|
||||||
|
wp_content_dir = options[:wp_content_dir]
|
||||||
|
url = options[:base_url]
|
||||||
|
type = options[:type]
|
||||||
|
targets_url = []
|
||||||
|
|
||||||
|
if only_vulnerable == false
|
||||||
|
# Open and parse the 'most popular' plugin list...
|
||||||
|
File.open(plugins_file, 'r') do |file|
|
||||||
|
file.readlines.collect do |line|
|
||||||
|
targets_url << WpPlugin.new(:base_url => url, :path => line.strip, :wp_content_dir => wp_content_dir)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
xml = Nokogiri::XML(File.open(plugin_vulns_file)) do |config|
|
||||||
|
config.noblanks
|
||||||
|
end
|
||||||
|
|
||||||
|
# We check if the plugin name from the plugin_vulns_file is already in targets, otherwise we add it
|
||||||
|
xml.xpath("//plugin").each do |node|
|
||||||
|
plugin_name = node.attribute('name').text
|
||||||
|
|
||||||
|
if targets_url.grep(%r{/#{plugin_name}/}).empty?
|
||||||
|
targets_url << WpPlugin.new(
|
||||||
|
:base_url => url,
|
||||||
|
:path => "#{type}/#{plugin_name}",
|
||||||
|
:wp_content_dir => wp_content_dir,
|
||||||
|
:name => plugin_name
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
targets_url.flatten!
|
||||||
|
targets_url.uniq!
|
||||||
|
# randomize the plugins array to *maybe* help in some crappy IDS/IPS/WAF detection
|
||||||
|
targets_url.sort_by! { rand }
|
||||||
|
end
|
||||||
|
end
|
||||||
50
lib/wpscan/wp_options.rb
Normal file
50
lib/wpscan/wp_options.rb
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#--
|
||||||
|
# WPScan - WordPress Security Scanner
|
||||||
|
# Copyright (C) 2012
|
||||||
|
#
|
||||||
|
# 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 WpOptions
|
||||||
|
def self.get_empty_options
|
||||||
|
options = {
|
||||||
|
:url => "",
|
||||||
|
:only_vulnerable_ones => true,
|
||||||
|
:file => "",
|
||||||
|
:vulns_file => "",
|
||||||
|
:vulns_xpath => "",
|
||||||
|
:wp_content_dir => "",
|
||||||
|
:show_progress_bar => true,
|
||||||
|
:error_404_hash => "",
|
||||||
|
:type => ""
|
||||||
|
}
|
||||||
|
options
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.check_options(options)
|
||||||
|
raise("url must be set") unless options[:url]
|
||||||
|
raise("only_vulnerable_ones must be set") unless options[:only_vulnerable_ones]
|
||||||
|
raise("file must be set") unless options[:file]
|
||||||
|
raise("vulns_file must be set") unless options[:vulns_file]
|
||||||
|
raise("vulns_xpath must be set") unless options[:vulns_xpath]
|
||||||
|
raise("wp_content_dir must be set") unless options[:wp_content_dir]
|
||||||
|
raise("show_progress_bar must be set") unless options[:show_progress_bar]
|
||||||
|
raise("error_404_hash must be set") unless options[:error_404_hash]
|
||||||
|
raise("type must be set") unless options[:type]
|
||||||
|
unless options[:type] =~ /plugins/i or options[:type] =~ /themes/i
|
||||||
|
raise("Unknown type #{options[:type]}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -19,38 +19,22 @@
|
|||||||
require "#{WPSCAN_LIB_DIR}/vulnerable"
|
require "#{WPSCAN_LIB_DIR}/vulnerable"
|
||||||
|
|
||||||
class WpPlugin < Vulnerable
|
class WpPlugin < Vulnerable
|
||||||
@@location_url_pattern = %r{^(https?://.*/([^/]+)/)}i
|
include WpItem
|
||||||
|
|
||||||
attr_reader :name
|
def initialize(options = {})
|
||||||
|
@base_url = options[:base_url]
|
||||||
def initialize(location_url, options = {})
|
@path = options[:path]
|
||||||
@location_uri = WpPlugin.location_uri_from_url(location_url)
|
@wp_content_dir = options[:wp_content_dir]
|
||||||
@name = options[:name] || WpPlugin.extract_name_from_location_url(location_url)
|
@name = options[:name] || extract_name_from_url(get_url)
|
||||||
@vulns_xml = options[:vulns_xml] || DATA_DIR + '/plugin_vulns.xml'
|
@vulns_xml = options[:vulns_xml] || DATA_DIR + '/plugin_vulns.xml'
|
||||||
@vulns_xpath = "//plugin[@name='#{@name}']/vulnerability"
|
@vulns_xpath = "//plugin[@name='#@name']/vulnerability"
|
||||||
end
|
@version = nil
|
||||||
|
|
||||||
def location_url
|
raise("base_url not set") unless @base_url
|
||||||
@location_uri.to_s
|
raise("path not set") unless @path
|
||||||
end
|
raise("wp_content_dir not set") unless @wp_content_dir
|
||||||
|
raise("name not set") unless @name
|
||||||
def ==(plugin)
|
raise("vulns_xml not set") unless @vulns_xml
|
||||||
plugin.name == @name
|
|
||||||
end
|
|
||||||
|
|
||||||
def <=>(plugin)
|
|
||||||
plugin.name <=> @name
|
|
||||||
end
|
|
||||||
|
|
||||||
# http://code.google.com/p/wpscan/issues/detail?id=97
|
|
||||||
def version
|
|
||||||
response = Browser.instance.get(@location_uri.merge("readme.txt").to_s)
|
|
||||||
response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1]
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
version = version()
|
|
||||||
"#{@name}#{' v' + version if version}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Discover any error_log files created by WordPress
|
# Discover any error_log files created by WordPress
|
||||||
@@ -64,39 +48,7 @@ class WpPlugin < Vulnerable
|
|||||||
end
|
end
|
||||||
|
|
||||||
def error_log_url
|
def error_log_url
|
||||||
@location_uri.merge("error_log").to_s
|
get_url.merge("error_log").to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
# Is directory listing enabled?
|
|
||||||
# WordPress denies directory listing however,
|
|
||||||
# forgets about the plugin directory.
|
|
||||||
def directory_listing?
|
|
||||||
Browser.instance.get(location_url()).body[%r{<title>Index of}] ? true : false
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.create_location_url_from_name(name, target_uri)
|
|
||||||
if target_uri.is_a?(String)
|
|
||||||
target_uri = URI.parse(target_uri)
|
|
||||||
end
|
|
||||||
target_uri.merge(URI.escape("$wp-plugins$/#{name}/")).to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.create_url_from_raw(raw, target_uri)
|
|
||||||
target_uri.merge(URI.escape("$wp-plugins$/#{raw}")).to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
def self.extract_name_from_location_url(location_url)
|
|
||||||
location_url[@@location_url_pattern, 2]
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.location_uri_from_url(location_url)
|
|
||||||
valid_location_url = location_url[%r{^(https?://.*/)[^.]+\.[^/]+$}, 1]
|
|
||||||
|
|
||||||
unless valid_location_url
|
|
||||||
valid_location_url = add_trailing_slash(location_url)
|
|
||||||
end
|
|
||||||
|
|
||||||
URI.parse(valid_location_url)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class WpTarget
|
|||||||
url = @uri.merge("wp-login.php").to_s
|
url = @uri.merge("wp-login.php").to_s
|
||||||
|
|
||||||
# Let's check if the login url is redirected (to https url for example)
|
# Let's check if the login url is redirected (to https url for example)
|
||||||
if redirection = redirection(url)
|
if redirection == redirection(url)
|
||||||
url = redirection
|
url = redirection
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -70,6 +70,11 @@ class WpTarget
|
|||||||
@error_404_hash
|
@error_404_hash
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Valid HTTP return codes
|
||||||
|
def self.valid_response_codes
|
||||||
|
[200, 403, 301, 302]
|
||||||
|
end
|
||||||
|
|
||||||
# return WpTheme
|
# return WpTheme
|
||||||
def theme
|
def theme
|
||||||
WpTheme.find(@uri)
|
WpTheme.find(@uri)
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ describe WpPlugin do
|
|||||||
@expected_uri_string = "http://example.com/wp-content/plugins/example/"
|
@expected_uri_string = "http://example.com/wp-content/plugins/example/"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should return the uri without the file" do
|
||||||
|
@url = "https://sub.example.com/path/to/dir/wp-content/plugins/example/readme.txt"
|
||||||
|
@expected_uri_string = "https://sub.example.com/path/to/dir/wp-content/plugins/example/"
|
||||||
|
end
|
||||||
|
|
||||||
it "should return the same uri" do
|
it "should return the same uri" do
|
||||||
@url = "http://example.com/wp-content/plugins/hello-world/"
|
@url = "http://example.com/wp-content/plugins/hello-world/"
|
||||||
@expected_uri_string = @url
|
@expected_uri_string = @url
|
||||||
@@ -65,6 +70,10 @@ describe WpPlugin do
|
|||||||
it "should return 'example-plugin'" do
|
it "should return 'example-plugin'" do
|
||||||
WpPlugin.extract_name_from_location_url('http://example.com/wp-content/plugins/example-plugin/').should === 'example-plugin'
|
WpPlugin.extract_name_from_location_url('http://example.com/wp-content/plugins/example-plugin/').should === 'example-plugin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should return 'example-plugin'" do
|
||||||
|
WpPlugin.extract_name_from_location_url('https://sub.example.com/path/to/a/wp-content/plugins/example-plugin/').should === 'example-plugin'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#create_location_url_from_name" do
|
describe "#create_location_url_from_name" do
|
||||||
|
|||||||
62
wpscan.rb
62
wpscan.rb
@@ -78,9 +78,7 @@ begin
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if wp_content_dir = wp_target.wp_content_dir()
|
unless wp_target.wp_content_dir
|
||||||
Browser.instance.variables_to_replace_in_url = {"$wp-content$" => wp_content_dir, "$wp-plugins$" => wp_target.wp_plugins_dir()}
|
|
||||||
else
|
|
||||||
raise "The wp_content_dir has not been found, please supply it with --wp-content-dir"
|
raise "The wp_content_dir has not been found, please supply it with --wp-content-dir"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -89,7 +87,7 @@ begin
|
|||||||
puts "| Started on #{Time.now.asctime}"
|
puts "| Started on #{Time.now.asctime}"
|
||||||
puts
|
puts
|
||||||
|
|
||||||
if wp_theme = wp_target.theme
|
if wp_theme == wp_target.theme
|
||||||
theme_version = wp_theme.version
|
theme_version = wp_theme.version
|
||||||
puts "[!] The WordPress theme in use is #{wp_theme}"
|
puts "[!] The WordPress theme in use is #{wp_theme}"
|
||||||
|
|
||||||
@@ -98,8 +96,8 @@ begin
|
|||||||
puts "[+] We have identified #{theme_vulnerabilities.size} vulnerabilities for this theme :"
|
puts "[+] We have identified #{theme_vulnerabilities.size} vulnerabilities for this theme :"
|
||||||
theme_vulnerabilities.each do |vulnerability|
|
theme_vulnerabilities.each do |vulnerability|
|
||||||
puts
|
puts
|
||||||
puts " | * Title: " + vulnerability.title
|
puts " | * Title: #{vulnerability.title}"
|
||||||
puts " | * Reference: " + vulnerability.reference
|
puts " | * Reference: #{vulnerability.reference}"
|
||||||
end
|
end
|
||||||
puts
|
puts
|
||||||
end
|
end
|
||||||
@@ -132,7 +130,7 @@ begin
|
|||||||
puts
|
puts
|
||||||
end
|
end
|
||||||
|
|
||||||
if wp_version = wp_target.version
|
if wp_version == wp_target.version
|
||||||
puts "[!] WordPress version #{wp_version.number} identified from #{wp_version.discovery_method}"
|
puts "[!] WordPress version #{wp_version.number} identified from #{wp_version.discovery_method}"
|
||||||
|
|
||||||
version_vulnerabilities = wp_version.vulnerabilities
|
version_vulnerabilities = wp_version.vulnerabilities
|
||||||
@@ -142,33 +140,33 @@ begin
|
|||||||
puts "[+] We have identified #{version_vulnerabilities.size} vulnerabilities from the version number :"
|
puts "[+] We have identified #{version_vulnerabilities.size} vulnerabilities from the version number :"
|
||||||
version_vulnerabilities.each do |vulnerability|
|
version_vulnerabilities.each do |vulnerability|
|
||||||
puts
|
puts
|
||||||
puts " | * Title: " + vulnerability.title
|
puts " | * Title: #{vulnerability.title}"
|
||||||
puts " | * Reference: " + vulnerability.reference
|
puts " | * Reference: #{vulnerability.reference}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil
|
if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil
|
||||||
puts
|
puts
|
||||||
print "[+] Enumerating plugins from passive detection ... "
|
puts "[+] Enumerating plugins from passive detection ... "
|
||||||
|
|
||||||
plugins = wp_target.plugins_from_passive_detection
|
plugins = wp_target.plugins_from_passive_detection
|
||||||
unless plugins.empty?
|
unless plugins.empty?
|
||||||
print "#{plugins.size} found :\n"
|
puts "#{plugins.size} found :"
|
||||||
|
|
||||||
plugins.each do |plugin|
|
plugins.each do |plugin|
|
||||||
puts
|
puts
|
||||||
puts " | Name: " + plugin.name
|
puts " | Name: #{plugin.name}"
|
||||||
puts " | Location: " + plugin.location_url.gsub("$wp-plugins$", wp_target.wp_plugins_dir()) #Hotfix
|
puts " | Location: #{plugin.get_url}"
|
||||||
|
|
||||||
plugin.vulnerabilities.each do |vulnerability|
|
plugin.vulnerabilities.each do |vulnerability|
|
||||||
puts " |"
|
puts " |"
|
||||||
puts " | [!] " + vulnerability.title
|
puts " | [!] #{vulnerability.title}"
|
||||||
puts " | * Reference: " + vulnerability.reference
|
puts " | * Reference: #{vulnerability.reference}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
print "No plugins found :(\n"
|
puts "No plugins found :("
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -178,20 +176,22 @@ begin
|
|||||||
puts "[+] Enumerating installed plugins #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_plugins} ..."
|
puts "[+] Enumerating installed plugins #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_plugins} ..."
|
||||||
puts
|
puts
|
||||||
|
|
||||||
plugins = wp_target.plugins_from_aggressive_detection(
|
options = WpOptions.get_empty_options
|
||||||
:only_vulnerable_ones => wpscan_options.enumerate_only_vulnerable_plugins,
|
options[:base_url] = wp_target.uri
|
||||||
:show_progress_bar => true
|
options[:only_vulnerable_ones] = wpscan_options.enumerate_only_vulnerable_plugins,
|
||||||
)
|
options[:show_progress_bar] = true,
|
||||||
|
options[:wp_content_dir] = wp_target.wp_content_dir
|
||||||
|
|
||||||
|
plugins = wp_target.plugins_from_aggressive_detection(options)
|
||||||
unless plugins.empty?
|
unless plugins.empty?
|
||||||
puts
|
puts
|
||||||
puts
|
puts
|
||||||
puts "[+] We found " + plugins.size.to_s + " plugins:"
|
puts "[+] We found #{plugins.size.to_s} plugins:"
|
||||||
|
|
||||||
plugins.each do |plugin|
|
plugins.each do |plugin|
|
||||||
puts
|
puts
|
||||||
puts " | Name: #{plugin}" #this will also output the version number if detected
|
puts " | Name: #{plugin}" #this will also output the version number if detected
|
||||||
puts " | Location: " + plugin.location_url.gsub("$wp-plugins$", wp_target.wp_plugins_dir()) #Hotfix
|
puts " | Location: #{plugin.get_url}"
|
||||||
|
|
||||||
puts " | Directory listing enabled? #{plugin.directory_listing? ? "Yes." : "No."}"
|
puts " | Directory listing enabled? #{plugin.directory_listing? ? "Yes." : "No."}"
|
||||||
|
|
||||||
plugin.vulnerabilities.each do |vulnerability|
|
plugin.vulnerabilities.each do |vulnerability|
|
||||||
@@ -199,8 +199,8 @@ begin
|
|||||||
#vulnerability['vulnerability'][0]['postdata'] == nil ? "" : postdata = CGI.unescapeHTML(vulnerability['vulnerability'][0]['postdata']) # postdata
|
#vulnerability['vulnerability'][0]['postdata'] == nil ? "" : postdata = CGI.unescapeHTML(vulnerability['vulnerability'][0]['postdata']) # postdata
|
||||||
|
|
||||||
puts " |"
|
puts " |"
|
||||||
puts " | [!] " + vulnerability.title
|
puts " | [!] #{vulnerability.title}"
|
||||||
puts " | * Reference: " + vulnerability.reference
|
puts " | * Reference: #{vulnerability.reference}"
|
||||||
|
|
||||||
# This has been commented out as MSF are moving from
|
# This has been commented out as MSF are moving from
|
||||||
# XML-RPC to MessagePack.
|
# XML-RPC to MessagePack.
|
||||||
@@ -212,7 +212,7 @@ begin
|
|||||||
end
|
end
|
||||||
|
|
||||||
if plugin.error_log?
|
if plugin.error_log?
|
||||||
puts " | [!] A WordPress error_log file has been found : " + plugin.error_log_url
|
puts " | [!] A WordPress error_log file has been found : #{plugin.error_log_url}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -230,11 +230,11 @@ begin
|
|||||||
timthumbs = wp_target.timthumbs
|
timthumbs = wp_target.timthumbs
|
||||||
|
|
||||||
puts
|
puts
|
||||||
puts "[+] We found " + timthumbs.size.to_s + " timthumb file/s :"
|
puts "[+] We found #{timthumbs.size.to_s} timthumb file/s :"
|
||||||
puts
|
puts
|
||||||
|
|
||||||
timthumbs.each do |file_url|
|
timthumbs.each do |file_url|
|
||||||
puts " | [!] " + file_url
|
puts " | [!] #{file_url}"
|
||||||
end
|
end
|
||||||
puts
|
puts
|
||||||
puts " * Reference: http://www.exploit-db.com/exploits/17602/"
|
puts " * Reference: http://www.exploit-db.com/exploits/17602/"
|
||||||
@@ -259,10 +259,10 @@ begin
|
|||||||
exit(1)
|
exit(1)
|
||||||
else
|
else
|
||||||
puts
|
puts
|
||||||
puts "We found the following " + usernames.length.to_s + " username/s :"
|
puts "We found the following #{usernames.length.to_s} username/s :"
|
||||||
puts
|
puts
|
||||||
|
|
||||||
usernames.each {|username| puts " " + username}
|
usernames.each {|username| puts " #{username}"}
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
@@ -296,7 +296,7 @@ begin
|
|||||||
end
|
end
|
||||||
|
|
||||||
puts
|
puts
|
||||||
puts '[+] Finished at ' + Time.now.asctime
|
puts "[+] Finished at #{Time.now.asctime}"
|
||||||
exit() # must exit!
|
exit() # must exit!
|
||||||
rescue => e
|
rescue => e
|
||||||
puts "[ERROR] #{e.message}"
|
puts "[ERROR] #{e.message}"
|
||||||
|
|||||||
Reference in New Issue
Block a user