Merge branch 'new-enumeration-system'
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 :
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
module WpPlugins
|
||||
|
||||
# Enumerate installed plugins.
|
||||
#
|
||||
# return array of WpPlugin
|
||||
def plugins_from_aggressive_detection(options)
|
||||
if options[:vulns_file].nil? or options[:vulns_file] == ''
|
||||
options[:vulns_file] = PLUGINS_VULNS_FILE
|
||||
end
|
||||
|
||||
options[:file] = options[:file] || (options[:full] ? PLUGINS_FULL_FILE : PLUGINS_FILE)
|
||||
options[:vulns_xpath] = "//plugin[@name='#{@name}']/vulnerability"
|
||||
options[:vulns_xpath_2] = '//plugin'
|
||||
options[:type] = 'plugins'
|
||||
result = WpDetector.aggressive_detection(options)
|
||||
plugins = []
|
||||
result.each do |r|
|
||||
plugins << WpPlugin.new(
|
||||
base_url: r.base_url,
|
||||
path: r.path,
|
||||
wp_content_dir: r.wp_content_dir,
|
||||
name: r.name,
|
||||
type: 'plugins',
|
||||
wp_plugins_dir: r.wp_plugins_dir
|
||||
)
|
||||
end
|
||||
plugins.sort_by { |p| p.name }
|
||||
end
|
||||
|
||||
# http://code.google.com/p/wpscan/issues/detail?id=42
|
||||
# plugins can be found in the source code :
|
||||
# <script src='http://example.com/wp-content/plugins/s2member/...' />
|
||||
# <link rel='stylesheet' href='http://example.com/wp-content/plugins/wp-minify/..' type='text/css' media='screen'/>
|
||||
# ...
|
||||
# return array of WpPlugin
|
||||
def plugins_from_passive_detection(options)
|
||||
plugins = []
|
||||
temp = WpDetector.passive_detection(options[:base_url], 'plugins', options[:wp_content_dir])
|
||||
|
||||
temp.each do |item|
|
||||
plugins << WpPlugin.new(
|
||||
base_url: item.base_url,
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
wp_content_dir: options[:wp_content_dir],
|
||||
type: 'plugins',
|
||||
wp_plugins_dir: options[:wp_plugins_dir]
|
||||
)
|
||||
end
|
||||
plugins.sort_by { |p| p.name }
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,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
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
module WpThemes
|
||||
|
||||
def themes_from_aggressive_detection(options)
|
||||
if options[:vulns_file].nil? or options[:vulns_file] == ''
|
||||
options[:vulns_file] = THEMES_VULNS_FILE
|
||||
end
|
||||
|
||||
options[:file] = options[:file] || (options[:full] ? THEMES_FULL_FILE : THEMES_FILE)
|
||||
options[:vulns_xpath] = "//theme[@name='#{@name}']/vulnerability"
|
||||
options[:vulns_xpath_2] = '//theme'
|
||||
options[:type] = 'themes'
|
||||
result = WpDetector.aggressive_detection(options)
|
||||
themes = []
|
||||
result.each do |r|
|
||||
themes << WpTheme.new(
|
||||
base_url: r.base_url,
|
||||
path: r.path,
|
||||
wp_content_dir: r.wp_content_dir,
|
||||
name: r.name
|
||||
)
|
||||
end
|
||||
themes.sort_by { |t| t.name }
|
||||
end
|
||||
|
||||
def themes_from_passive_detection(options)
|
||||
themes = []
|
||||
temp = WpDetector.passive_detection(options[:base_url], 'themes', options[:wp_content_dir])
|
||||
|
||||
temp.each do |item|
|
||||
themes << WpTheme.new(
|
||||
base_url: item.base_url,
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
wp_content_dir: options[:wp_content_dir]
|
||||
)
|
||||
end
|
||||
themes.sort_by { |t| t.name }
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,74 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
module WpTimthumbs
|
||||
|
||||
# Used as cache :
|
||||
# nil => timthumbs not checked,
|
||||
# [] => no timthumbs,
|
||||
# otherwise array of timthumbs url found
|
||||
@wp_timthumbs = nil
|
||||
|
||||
def has_timthumbs?(theme_name, options = {})
|
||||
!timthumbs(theme_name, options).empty?
|
||||
end
|
||||
|
||||
def timthumbs(theme_name = nil, options = {})
|
||||
if @wp_timthumbs.nil?
|
||||
options[:type] = 'timthumbs'
|
||||
options[:only_vulnerable_ones] = false
|
||||
options[:file] = options[:file] || DATA_DIR + '/timthumbs.txt'
|
||||
options[:vulns_file] = 'xxx'
|
||||
options[:vulns_xpath] = 'xxx'
|
||||
options[:vulns_xpath_2] = 'xxx'
|
||||
|
||||
WpOptions.check_options(options)
|
||||
if theme_name == nil
|
||||
custom_items = nil
|
||||
else
|
||||
custom_items = targets_url_from_theme(theme_name, options)
|
||||
end
|
||||
@wp_timthumbs = WpEnumerator.enumerate(options, custom_items)
|
||||
end
|
||||
@wp_timthumbs
|
||||
end
|
||||
|
||||
protected
|
||||
def targets_url_from_theme(theme_name, options)
|
||||
targets = []
|
||||
theme_name = URI.escape(theme_name)
|
||||
|
||||
%w{
|
||||
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
|
||||
}.each do |file|
|
||||
targets << WpItem.new(
|
||||
base_url: options[:base_url],
|
||||
path: "themes/#{theme_name}/#{file}",
|
||||
wp_content_dir: options[:wp_content_dir],
|
||||
name: theme_name,
|
||||
vulns_file: 'XX',
|
||||
type: 'timthumbs',
|
||||
wp_plugins_dir: options[:wp_plugins_dir]
|
||||
)
|
||||
end
|
||||
targets
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,117 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
module WpUsernames
|
||||
|
||||
# Enumerate wordpress usernames by using Veronica Valeros's technique:
|
||||
# http://seclists.org/fulldisclosure/2011/May/493
|
||||
#
|
||||
# Available options :
|
||||
# :range - default : 1..10
|
||||
#
|
||||
# returns an array of WpUser (can be empty)
|
||||
def usernames(options = {})
|
||||
range = options[:range] || (1..10)
|
||||
browser = Browser.instance
|
||||
usernames = []
|
||||
|
||||
range.each do |author_id|
|
||||
url = author_url(author_id)
|
||||
response = browser.get(url)
|
||||
|
||||
username = nil
|
||||
nickname = nil
|
||||
if response.code == 301 # username in location?
|
||||
username = response.headers_hash['location'][%r{/author/([^/\b]+)/?}i, 1]
|
||||
# Get the real name from the redirect site
|
||||
nickname = get_nickname_from_url(url)
|
||||
elsif response.code == 200 # username in body?
|
||||
# get the username from the author feed URL
|
||||
username = get_username_from_response(response)
|
||||
nickname = get_nickname_from_response(response)
|
||||
end
|
||||
|
||||
unless username == nil and nickname == nil
|
||||
usernames << WpUser.new(username, author_id, nickname)
|
||||
end
|
||||
end
|
||||
usernames = remove_junk_from_nickname(usernames)
|
||||
|
||||
# clean the array, remove nils and possible duplicates
|
||||
usernames.flatten!
|
||||
usernames.compact!
|
||||
usernames.uniq
|
||||
end
|
||||
|
||||
def get_nickname_from_url(url)
|
||||
resp = Browser.instance.get_and_follow_location(url)
|
||||
nickname = nil
|
||||
if resp.code == 200
|
||||
nickname = extract_nickname_from_body(resp.body)
|
||||
end
|
||||
nickname
|
||||
end
|
||||
|
||||
def get_nickname_from_response(resp)
|
||||
nickname = nil
|
||||
if resp.code == 200
|
||||
nickname = extract_nickname_from_body(resp.body)
|
||||
end
|
||||
nickname
|
||||
end
|
||||
|
||||
def get_username_from_response(resp)
|
||||
# Feed URL with Permalinks
|
||||
username = resp.body[%r{/author/([^/\b]+)/?}i, 1]
|
||||
if username.nil?
|
||||
# No Permalinks
|
||||
username = resp.body[%r{<body class="archive author author-([^\s]+) author-(\d+)}i, 1]
|
||||
end
|
||||
username
|
||||
end
|
||||
|
||||
def extract_nickname_from_body(body)
|
||||
body[%r{<title>([^<]*)</title>}i, 1]
|
||||
end
|
||||
|
||||
def remove_junk_from_nickname(usernames)
|
||||
unless usernames.kind_of? Array
|
||||
raise('Need an array as input')
|
||||
end
|
||||
nicknames = []
|
||||
usernames.each do |u|
|
||||
unless u.kind_of? WpUser
|
||||
raise('Items must be of type WpUser')
|
||||
end
|
||||
nickname = u.nickname
|
||||
unless nickname == 'empty'
|
||||
nicknames << nickname
|
||||
end
|
||||
end
|
||||
junk = get_equal_string_end(nicknames)
|
||||
usernames.each do |u|
|
||||
u.nickname = u.nickname.sub(/#{Regexp.escape(junk)}$/, '')
|
||||
end
|
||||
usernames
|
||||
end
|
||||
|
||||
def author_url(author_id)
|
||||
@uri.merge("?author=#{author_id}").to_s
|
||||
end
|
||||
end
|
||||
@@ -1,40 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
class Vulnerable
|
||||
|
||||
attr_reader :vulns_file, :vulns_xpath
|
||||
|
||||
# @return an array of WpVulnerability (can be empty)
|
||||
def vulnerabilities
|
||||
xml = xml(@vulns_file)
|
||||
vulnerabilities = []
|
||||
|
||||
xml.xpath(@vulns_xpath).each do |node|
|
||||
vulnerabilities << WpVulnerability.new(
|
||||
node.search('title').text,
|
||||
node.search('reference').map(&:text),
|
||||
node.search('type').text,
|
||||
node.search('metasploit').map(&:text)
|
||||
)
|
||||
end
|
||||
vulnerabilities
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,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
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
class WpDetector
|
||||
|
||||
def self.aggressive_detection(options, items = [])
|
||||
WpOptions.check_options(options)
|
||||
|
||||
result = items
|
||||
if items == nil or items.length == 0
|
||||
unless options[:only_vulnerable_ones]
|
||||
result = passive_detection(options[:base_url], options[:type], options[:wp_content_dir])
|
||||
end
|
||||
end
|
||||
|
||||
enum_results = WpEnumerator.enumerate(options)
|
||||
enum_results.each do |enum_result|
|
||||
already_present = false
|
||||
result.each do |r|
|
||||
# Already found via passive detection
|
||||
if r.name == enum_result.name
|
||||
already_present = true
|
||||
break
|
||||
end
|
||||
end
|
||||
unless already_present
|
||||
result << enum_result
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# plugins and themes can be found in the source code :
|
||||
# <script src='http://example.com/wp-content/plugins/s2member/...' />
|
||||
# <link rel='stylesheet' href='http://example.com/wp-content/plugins/wp-minify/..' type='text/css' media='screen'/>
|
||||
# ...
|
||||
def self.passive_detection(url, type, wp_content_dir)
|
||||
items = []
|
||||
response = Browser.instance.get(url)
|
||||
regex1 = %r{(?:[^=:]+)\s?(?:=|:)\s?(?:"|')[^"']+\\?/}
|
||||
regex2 = %r{\\?/}
|
||||
regex3 = %r{\\?/([^/\\"']+)\\?(?:/|"|')}
|
||||
# Custom wp-content dir is now used in this regex
|
||||
names = response.body.scan(/#{regex1}#{Regexp.escape(wp_content_dir)}#{regex2}#{Regexp.escape(type)}#{regex3}/i)
|
||||
|
||||
names.flatten!
|
||||
names.uniq!
|
||||
|
||||
names.each do |item|
|
||||
items << WpItem.new(
|
||||
base_url: url,
|
||||
name: item,
|
||||
type: type,
|
||||
path: "#{item}/",
|
||||
wp_content_dir: wp_content_dir,
|
||||
vulns_file: ''
|
||||
)
|
||||
end
|
||||
items
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,146 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
# Enumerate over a given set of items and check if they exist
|
||||
class WpEnumerator
|
||||
|
||||
# Enumerate the given Targets
|
||||
#
|
||||
# ==== Attributes
|
||||
#
|
||||
# * +targets+ - targets to enumerate
|
||||
# * * +:base_url+ - Base URL
|
||||
# * * +:wp_content+ - wp-content directory
|
||||
# * * +:path+ - Path to plugin
|
||||
# * +type+ - "plugins" or "themes", item to enumerate
|
||||
# * +filename+ - filename in the data directory with paths
|
||||
# * +show_progression+ - Show a progress bar during enumeration
|
||||
def self.enumerate(options = {}, items = nil)
|
||||
|
||||
WpOptions.check_options(options)
|
||||
|
||||
targets = self.generate_items(options)
|
||||
|
||||
unless items == nil
|
||||
items.each do |i|
|
||||
targets << i
|
||||
end
|
||||
end
|
||||
|
||||
found = []
|
||||
queue_count = 0
|
||||
request_count = 0
|
||||
enum_browser = Browser.instance
|
||||
enum_hydra = enum_browser.hydra
|
||||
enumerate_size = targets.size
|
||||
exclude_regexp = options[:exclude_content_based] ? %r{#{options[:exclude_content_based]}} : nil
|
||||
show_progression = options[:show_progression] || false
|
||||
|
||||
targets.each do |target|
|
||||
url = target.get_full_url
|
||||
|
||||
request = enum_browser.forge_request(url, cache_ttl: 0, followlocation: true)
|
||||
request_count += 1
|
||||
|
||||
request.on_complete do |response|
|
||||
page_hash = Digest::MD5.hexdigest(response.body)
|
||||
|
||||
print "\rChecking for #{enumerate_size} total #{options[:type]}... #{(request_count * 100) / enumerate_size}% complete." if show_progression
|
||||
|
||||
if WpTarget.valid_response_codes.include?(response.code)
|
||||
if page_hash != options[:error_404_hash] and page_hash != options[:homepage_hash]
|
||||
if options[:exclude_content_based]
|
||||
unless response.body[exclude_regexp]
|
||||
found << target
|
||||
end
|
||||
else
|
||||
found << target
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
enum_hydra.queue(request)
|
||||
queue_count += 1
|
||||
|
||||
if queue_count == enum_browser.max_threads
|
||||
enum_hydra.run
|
||||
queue_count = 0
|
||||
end
|
||||
end
|
||||
|
||||
enum_hydra.run
|
||||
found
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.generate_items(options = {})
|
||||
only_vulnerable = options[:only_vulnerable_ones]
|
||||
file = options[:file]
|
||||
vulns_file = options[:vulns_file]
|
||||
wp_content_dir = options[:wp_content_dir]
|
||||
url = options[:base_url]
|
||||
type = options[:type]
|
||||
plugins_dir = options[:wp_plugins_dir]
|
||||
targets_url = []
|
||||
|
||||
unless only_vulnerable
|
||||
# Open and parse the 'most popular' plugin list...
|
||||
File.open(file, 'r') do |f|
|
||||
f.readlines.collect do |line|
|
||||
l = line.strip
|
||||
targets_url << WpItem.new(
|
||||
base_url: url,
|
||||
path: l,
|
||||
wp_content_dir: wp_content_dir,
|
||||
name: l =~ /.+\/.+/ ? File.dirname(l) : l.sub(/\/$/, ''),
|
||||
vulns_file: vulns_file,
|
||||
type: type,
|
||||
wp_plugins_dir: plugins_dir
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Timthumbs have no XML file
|
||||
unless type =~ /timthumbs/i
|
||||
xml = xml(vulns_file)
|
||||
|
||||
# We check if the plugin name from the plugin_vulns_file is already in targets, otherwise we add it
|
||||
xml.xpath(options[:vulns_xpath_2]).each do |node|
|
||||
name = node.attribute('name').text
|
||||
targets_url << WpItem.new(
|
||||
base_url: url,
|
||||
path: name,
|
||||
wp_content_dir: wp_content_dir,
|
||||
name: name,
|
||||
vulns_file: vulns_file,
|
||||
type: type,
|
||||
wp_plugins_dir: plugins_dir
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
targets_url.flatten! { |t| t.name }
|
||||
targets_url.uniq! { |t| t.name }
|
||||
# randomize the plugins array to *maybe* help in some crappy IDS/IPS/WAF detection
|
||||
targets_url.sort_by! { rand }
|
||||
end
|
||||
end
|
||||
@@ -1,196 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require "wpscan/vulnerable"
|
||||
|
||||
class WpItem < Vulnerable
|
||||
attr_reader :base_url, :path, :wp_content_dir, :name, :vulns_file, :vulns_xpath, :wp_plugins_dir, :type
|
||||
@version = nil
|
||||
|
||||
def initialize(options)
|
||||
@type = options[:type]
|
||||
@wp_content_dir = options[:wp_content_dir] ? options[:wp_content_dir].sub(/^\//, '').sub(/\/$/, '') : 'wp-content'
|
||||
@wp_plugins_dir = options[:wp_plugins_dir] || "#@wp_content_dir/plugins"
|
||||
@base_url = options[:base_url]
|
||||
@path = options[:path]
|
||||
@name = options[:name] || extract_name_from_url
|
||||
@vulns_file = options[:vulns_file]
|
||||
@vulns_xpath = options[:vulns_xpath].sub(/\$name\$/, @name) unless options[:vulns_xpath] == nil
|
||||
|
||||
raise('base_url not set') unless @base_url
|
||||
raise('path not set') unless @path
|
||||
raise('wp_content_dir not set') unless @wp_content_dir
|
||||
raise('name not set') unless @name
|
||||
raise('vulns_file not set') unless @vulns_file
|
||||
raise('type not set') unless @type
|
||||
end
|
||||
|
||||
# The wordpress.org plugins directory URL
|
||||
# See: https://github.com/wpscanteam/wpscan/issues/100
|
||||
def wp_org_url
|
||||
case @type
|
||||
when 'themes'
|
||||
return URI('http://wordpress.org/extend/themes/').merge("#@name/")
|
||||
when 'plugins'
|
||||
return URI('http://wordpress.org/extend/plugins/').merge("#@name/")
|
||||
else
|
||||
raise("No Wordpress URL for #@type")
|
||||
end
|
||||
end
|
||||
|
||||
# returns true if this theme or plugin is hosted on wordpress.org
|
||||
def wp_org_item?
|
||||
case @type
|
||||
when 'themes'
|
||||
file = THEMES_FULL_FILE
|
||||
when 'plugins'
|
||||
file = PLUGINS_FULL_FILE
|
||||
else
|
||||
raise("Unknown type #@type")
|
||||
end
|
||||
f = File.readlines(file, encoding: 'UTF-8').grep(/^#{Regexp.escape(@name)}$/i)
|
||||
f.empty? ? false : true
|
||||
end
|
||||
|
||||
def get_sub_folder
|
||||
case @type
|
||||
when 'themes'
|
||||
folder = 'themes'
|
||||
when 'timthumbs'
|
||||
# not needed
|
||||
folder = nil
|
||||
else
|
||||
raise("unknown type #@type")
|
||||
end
|
||||
folder
|
||||
end
|
||||
|
||||
# Get the full url for this item
|
||||
def get_full_url
|
||||
url = @base_url.to_s.end_with?('/') ? @base_url.to_s : "#@base_url/"
|
||||
# remove first and last /
|
||||
wp_content_dir = @wp_content_dir.sub(/^\//, "").sub(/\/$/, '')
|
||||
# remove first /
|
||||
path = @path.sub(/^\//, '')
|
||||
if type == 'plugins'
|
||||
# plugins can be outside of wp-content. wp_content_dir included in wp_plugins_dir
|
||||
ret = URI.parse(URI.encode("#{url}#@wp_plugins_dir/#{path}"))
|
||||
elsif type == 'timthumbs'
|
||||
# timthumbs have folder in path variable
|
||||
ret = URI.parse(URI.encode("#{url}#{wp_content_dir}/#{path}"))
|
||||
else
|
||||
ret = URI.parse(URI.encode("#{url}#{wp_content_dir}/#{get_sub_folder}/#{path}"))
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# Gets the full url for this item without filenames
|
||||
def get_url_without_filename
|
||||
location_url = get_full_url.to_s
|
||||
valid_location_url = location_url[%r{^(https?://.*/)[^.]+\.[^/]+$}, 1]
|
||||
unless valid_location_url
|
||||
valid_location_url = add_trailing_slash(location_url)
|
||||
end
|
||||
URI.parse(URI.encode(valid_location_url))
|
||||
end
|
||||
|
||||
# Returns version number from readme.txt if it exists
|
||||
def version
|
||||
unless @version
|
||||
response = Browser.instance.get(readme_url.to_s)
|
||||
@version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1]
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
# Is directory listing enabled?
|
||||
def directory_listing?
|
||||
# Need to remove to file part from the url
|
||||
Browser.instance.get(get_url_without_filename).body[%r{<title>Index of}] ? true : false
|
||||
end
|
||||
|
||||
# Extract item name from a url
|
||||
def extract_name_from_url
|
||||
get_full_url.to_s[%r{^(https?://.*/([^/]+)/)}i, 2]
|
||||
end
|
||||
|
||||
# To string. Adds a version number if detected
|
||||
def to_s
|
||||
item_version = version
|
||||
"#@name#{' v' + item_version.strip if item_version}"
|
||||
end
|
||||
|
||||
# Compare
|
||||
def ==(other)
|
||||
other.name == self.name
|
||||
end
|
||||
|
||||
# Compare
|
||||
def ===(other)
|
||||
other.name == self.name
|
||||
end
|
||||
|
||||
# Compare
|
||||
def <=>(other)
|
||||
other.name <=> self.name
|
||||
end
|
||||
|
||||
# Url for readme.txt
|
||||
def readme_url
|
||||
get_url_without_filename.merge('readme.txt')
|
||||
end
|
||||
|
||||
# Url for changelog.txt
|
||||
def changelog_url
|
||||
get_url_without_filename.merge('changelog.txt')
|
||||
end
|
||||
|
||||
def error_log_url
|
||||
get_url_without_filename.merge('error_log')
|
||||
end
|
||||
|
||||
# Discover any error_log files created by WordPress
|
||||
# These are created by the WordPress error_log() function
|
||||
# They are normally found in the /plugins/ directory,
|
||||
# however can also be found in their specific plugin dir.
|
||||
# http://www.exploit-db.com/ghdb/3714/
|
||||
def error_log?
|
||||
response_body = Browser.instance.get(error_log_url, headers: {'range' => 'bytes=0-700'}).body
|
||||
response_body[%r{PHP Fatal error}i] ? true : false
|
||||
end
|
||||
|
||||
# readme.txt present?
|
||||
def has_readme?
|
||||
unless @readme
|
||||
status = Browser.instance.get(readme_url).code
|
||||
@readme = status == 200 ? true : false
|
||||
end
|
||||
@readme
|
||||
end
|
||||
|
||||
# changelog.txt present?
|
||||
def has_changelog?
|
||||
unless @changelog
|
||||
status = Browser.instance.get(changelog_url).code
|
||||
@changelog = status == 200 ? true : false
|
||||
end
|
||||
@changelog
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,52 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
# Options Hash
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * +url+ - The base URL of the WordPress site
|
||||
# * +only_vulnerable_ones+ - Only detect vulnerable items
|
||||
# * +file+ - Filename with items to detect
|
||||
# * +vulns_file+ - XML file with vulnerabilities
|
||||
# * +vulns_xpath+ - XPath for vulnerability XML file
|
||||
# * +vulns_xpath_2+ - XPath for vulnerability XML file
|
||||
# * +wp_content_dir+ - Name of the wp-content directory
|
||||
# * +show_progression+ - Show a progress bar during enumeration
|
||||
# * +error_404_hash+ - MD5 hash of a 404 page
|
||||
# * +type+ - Type: plugins, themes
|
||||
class WpOptions
|
||||
def self.check_options(options)
|
||||
raise('base_url must be set') unless options[:base_url] != nil and options[:base_url].to_s.length > 0
|
||||
raise('only_vulnerable_ones must be set') unless options[:only_vulnerable_ones] != nil
|
||||
raise('file must be set') unless options[:file] != nil and options[:file].length > 0
|
||||
raise('vulns_file must be set') unless options[:vulns_file] != nil and options[:vulns_file].length > 0
|
||||
raise('vulns_xpath must be set') unless options[:vulns_xpath] != nil and options[:vulns_xpath].length > 0
|
||||
raise('vulns_xpath_2 must be set') unless options[:vulns_xpath_2] != nil and options[:vulns_xpath_2].length > 0
|
||||
raise('wp_content_dir must be set') unless options[:wp_content_dir] != nil and options[:wp_content_dir].length > 0
|
||||
raise('show_progression must be set') unless options[:show_progression] != nil
|
||||
raise('error_404_hash must be set') unless options[:error_404_hash] != nil and options[:error_404_hash].length > 0
|
||||
raise('type must be set') unless options[:type] != nil and options[:type].length > 0
|
||||
|
||||
unless options[:type] =~ /plugins/i or options[:type] =~ /themes/i or options[:type] =~ /timthumbs/i
|
||||
raise("Unknown type #{options[:type]}")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,32 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
class WpPlugin < WpItem
|
||||
def initialize(options = {})
|
||||
if options[:vulns_file].nil? or options[:vulns_file] == ''
|
||||
options[:vulns_file] = PLUGINS_VULNS_FILE
|
||||
end
|
||||
|
||||
options[:vulns_xpath] = "//plugin[@name='$name$']/vulnerability"
|
||||
options[:vulns_xpath_2] = '//plugin'
|
||||
options[:type] = 'plugins'
|
||||
|
||||
super(options)
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require "wpscan/vulnerable"
|
||||
|
||||
class WpTheme < WpItem
|
||||
|
||||
attr_reader :style_url, :version
|
||||
|
||||
def initialize(options = {})
|
||||
if options[:vulns_file].nil? or options[:vulns_file] == ''
|
||||
options[:vulns_file] = THEMES_VULNS_FILE
|
||||
end
|
||||
|
||||
options[:vulns_xpath] = "//theme[@name='$name$']/vulnerability"
|
||||
options[:type] = 'themes'
|
||||
@version = options[:version]
|
||||
@style_url = options[:style_url]
|
||||
|
||||
super(options)
|
||||
end
|
||||
|
||||
def version
|
||||
unless @version
|
||||
if @style_url
|
||||
url = @style_url
|
||||
else
|
||||
url = default_style_url
|
||||
end
|
||||
@version = Browser.instance.get(url).body[%r{Version:\s([^\s]+)}i, 1]
|
||||
|
||||
# Get Version from readme.txt
|
||||
if @version.nil?
|
||||
@version = super
|
||||
end
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
def default_style_url
|
||||
get_url_without_filename.merge('style.css')
|
||||
end
|
||||
|
||||
def self.find(target_uri)
|
||||
self.methods.grep(/find_from_/).each do |method_to_call|
|
||||
theme = self.send(method_to_call, target_uri)
|
||||
|
||||
return theme if theme
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def ===(wp_theme)
|
||||
wp_theme.name === @name and wp_theme.version === @version
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Discover the wordpress theme name by parsing the css link rel
|
||||
def self.find_from_css_link(target_uri)
|
||||
response = Browser.instance.get_and_follow_location(target_uri.to_s)
|
||||
|
||||
# https + domain is optional because of relative links
|
||||
matches = %r{(?:https?://[^"']+)?/([^/]+)/themes/([^"']+)/style.css}i.match(response.body)
|
||||
if matches
|
||||
style_url = matches[0]
|
||||
wp_content_dir = matches[1]
|
||||
theme_name = matches[2]
|
||||
|
||||
return new(
|
||||
name: theme_name,
|
||||
style_url: style_url,
|
||||
base_url: target_uri,
|
||||
path: theme_name,
|
||||
wp_content_dir: wp_content_dir
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# http://code.google.com/p/wpscan/issues/detail?id=141
|
||||
def self.find_from_wooframework(target_uri)
|
||||
body = Browser.instance.get(target_uri.to_s).body
|
||||
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
|
||||
|
||||
matches = regexp.match(body)
|
||||
if matches
|
||||
woo_theme_name = matches[1]
|
||||
woo_theme_version = matches[2]
|
||||
woo_framework_version = matches[3] # Not used at this time
|
||||
|
||||
return new(
|
||||
name: woo_theme_name,
|
||||
version: woo_theme_version,
|
||||
base_url: target_uri.to_s,
|
||||
path: woo_theme_name
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,76 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
class WpUser
|
||||
|
||||
def name
|
||||
if @name.nil? or @name.to_s.strip.empty?
|
||||
return 'empty'
|
||||
end
|
||||
@name
|
||||
end
|
||||
|
||||
def name=(new_name)
|
||||
@name = new_name
|
||||
end
|
||||
|
||||
def id
|
||||
if @id.nil? or @id.to_s.strip.empty?
|
||||
return 'empty'
|
||||
end
|
||||
@id
|
||||
end
|
||||
|
||||
def id=(new_id)
|
||||
@id = new_id
|
||||
end
|
||||
|
||||
def nickname
|
||||
if @nickname.nil? or @nickname.to_s.strip.empty?
|
||||
return 'empty'
|
||||
end
|
||||
@nickname
|
||||
end
|
||||
|
||||
def nickname=(new_nickname)
|
||||
@nickname = new_nickname
|
||||
end
|
||||
|
||||
def initialize(name, id, nickname)
|
||||
self.name = name
|
||||
self.id = id
|
||||
self.nickname = nickname
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
other.name <=> self.name
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self === other
|
||||
end
|
||||
|
||||
def ===(other)
|
||||
other.name === self.name and other.id === self.id and other.nickname === self.nickname
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
self === other
|
||||
end
|
||||
end
|
||||
@@ -1,202 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
require "wpscan/vulnerable"
|
||||
|
||||
class WpVersion < Vulnerable
|
||||
|
||||
attr_reader :number, :discovery_method
|
||||
|
||||
def initialize(number, options = {})
|
||||
@number = number
|
||||
@discovery_method = options[:discovery_method]
|
||||
@vulns_file = options[:vulns_file] || WP_VULNS_FILE
|
||||
@vulns_xpath = "//wordpress[@version='#{@number}']/vulnerability"
|
||||
end
|
||||
|
||||
# Will use all method self.find_from_* to try to detect the version
|
||||
# Once the version is found, it will return a WpVersion object
|
||||
# The method_name will be without 'find_from_' and '_' will be replace by ' ' (IE 'meta generator', 'rss generator' etc)
|
||||
# If the version is not found, nil is returned
|
||||
#
|
||||
# The order in which the find_from_* methods are is important, they will be called in the same order
|
||||
# (find_from_meta_generator, find_from_rss_generator etc)
|
||||
def self.find(target_uri, wp_content_dir)
|
||||
options = {
|
||||
base_uri: target_uri,
|
||||
wp_content_dir: wp_content_dir
|
||||
}
|
||||
self.methods.grep(/find_from_/).each do |method_to_call|
|
||||
version = self.send(method_to_call, options)
|
||||
|
||||
if version
|
||||
return new(version, discovery_method: method_to_call[%r{find_from_(.*)}, 1].gsub('_', ' '))
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Returns the first match of <pattern> in the body of the url
|
||||
def self.scan_url(base_uri, pattern, path = nil)
|
||||
url = path ? base_uri.merge(path).to_s : base_uri.to_s
|
||||
response = Browser.instance.get_and_follow_location(url)
|
||||
|
||||
response.body[pattern, 1]
|
||||
end
|
||||
|
||||
#
|
||||
# DO NOT Change the order of the following methods
|
||||
# unless you know what you are doing
|
||||
# See WpVersion.find
|
||||
#
|
||||
|
||||
# Attempts to find the wordpress version from,
|
||||
# the generator meta tag in the html source.
|
||||
#
|
||||
# The meta tag can be removed however it seems,
|
||||
# that it is reinstated on upgrade.
|
||||
def self.find_from_meta_generator(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{name="generator" content="wordpress #{WpVersion.version_pattern}"}i
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS feed source.
|
||||
def self.find_from_rss_generator(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{<generator>http://wordpress.org/\?v=#{WpVersion.version_pattern}</generator>}i,
|
||||
'feed/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find WordPress version from,
|
||||
# the generator tag in the RDF feed source.
|
||||
def self.find_from_rdf_generator(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{WpVersion.version_pattern}" />}i,
|
||||
'feed/rdf/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS2 feed source.
|
||||
#
|
||||
# Have not been able to find an example of this - Ryan
|
||||
#def self.find_from_rss2_generator(options)
|
||||
# WpVersion.scan_url(
|
||||
# options[:base_uri],
|
||||
# %r{<generator>http://wordpress.org/?v=(#{WpVersion.version_pattern})</generator>}i,
|
||||
# 'feed/rss/'
|
||||
# )
|
||||
#end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the Atom source.
|
||||
def self.find_from_atom_generator(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{<generator uri="http://wordpress.org/" version="#{WpVersion.version_pattern}">WordPress</generator>}i,
|
||||
'feed/atom/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the comment rss source.
|
||||
#
|
||||
# Have not been able to find an example of this - Ryan
|
||||
#def self.find_from_comments_rss_generator(options)
|
||||
# WpVersion.scan_url(
|
||||
# options[:base_uri],
|
||||
# %r{<!-- generator="WordPress/#{WpVersion.version_pattern}" -->}i,
|
||||
# 'comments/feed/'
|
||||
# )
|
||||
#end
|
||||
|
||||
# Uses data/wp_versions.xml to try to identify a
|
||||
# wordpress version.
|
||||
#
|
||||
# It does this by using client side file hashing
|
||||
#
|
||||
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
|
||||
#
|
||||
def self.find_from_advanced_fingerprinting(options)
|
||||
target_uri = options[:base_uri]
|
||||
version_xml = options[:version_xml] || WP_VERSIONS_FILE # needed for rpsec
|
||||
wp_content = options[:wp_content_dir]
|
||||
wp_plugins = "#{wp_content}/plugins"
|
||||
xml = xml(version_xml)
|
||||
|
||||
xml.xpath('//file').each do |node|
|
||||
file_src = node.attribute('src').text
|
||||
file_url = target_uri.merge(file_src).to_s.
|
||||
gsub(/\$wp-plugins\$/i, wp_plugins).
|
||||
gsub(/\$wp-content\$/i, wp_content)
|
||||
|
||||
response = Browser.instance.get(file_url)
|
||||
md5sum = Digest::MD5.hexdigest(response.body)
|
||||
|
||||
node.search('hash').each do |hash|
|
||||
if hash.attribute('md5').text == md5sum
|
||||
return hash.search('version').text
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the readme.html file.
|
||||
def self.find_from_readme(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{<br />\sversion #{WpVersion.version_pattern}}i,
|
||||
'readme.html'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the sitemap.xml file.
|
||||
#
|
||||
# See: http://code.google.com/p/wpscan/issues/detail?id=109
|
||||
def self.find_from_sitemap_generator(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{generator="wordpress/#{WpVersion.version_pattern}"}i,
|
||||
'sitemap.xml'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the p-links-opml.php file.
|
||||
def self.find_from_links_opml(options)
|
||||
WpVersion.scan_url(
|
||||
options[:base_uri],
|
||||
%r{generator="wordpress/#{WpVersion.version_pattern}"}i,
|
||||
'wp-links-opml.php'
|
||||
)
|
||||
end
|
||||
|
||||
# Used to check if the version is correct: must contain at least one dot.
|
||||
def self.version_pattern
|
||||
'([^\r\n"\']+\.[^\r\n"\']+)'
|
||||
end
|
||||
end
|
||||
@@ -1,29 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
#--
|
||||
# WPScan - WordPress Security Scanner
|
||||
# Copyright (C) 2012-2013
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#++
|
||||
|
||||
class WpVulnerability
|
||||
attr_accessor :title, :references, :type, :metasploit_modules
|
||||
|
||||
def initialize(title, references, type, metasploit_modules)
|
||||
@title = title
|
||||
@references = references
|
||||
@type = type
|
||||
@metasploit_modules = metasploit_modules
|
||||
end
|
||||
end
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user