Compare commits

...

60 Commits
2.7 ... 2.8

Author SHA1 Message Date
ethicalhack3r
5902a483b4 Ready for release version 2.8 #834 2015-06-22 18:56:37 +02:00
Christian Mehlmauer
ca73e4b93e fix some code styling issues 2015-06-21 11:05:25 +02:00
Christian Mehlmauer
ace64d88ce Merge branch 'master' of github.com:wpscanteam/wpscan 2015-06-21 11:03:55 +02:00
Christian Mehlmauer
4cc9f7c8b5 merge 2015-06-21 11:03:51 +02:00
Christian Mehlmauer
f4f1390b67 fix some code styling issues 2015-06-21 10:59:57 +02:00
erwanlr
14115761f9 Uses the URI.join to determine the redirection URL - Fix #829 2015-06-18 20:48:43 +01:00
Peter
ac3409e376 Update CHANGELOG 2015-06-18 21:07:12 +02:00
Ryan Dewhurst
2657e5050f Merge pull request #830 from mrnfrancesco/fix-issue-815
Fix issue 815
2015-06-04 09:46:26 +02:00
ethicalhack3r
3d6e5b2b9e Continue if user chooses not to update + db exists 2015-06-03 16:42:23 +02:00
ethicalhack3r
bdd6b9727d Dont update if user chooses default + no DBs exist 2015-06-03 16:40:04 +02:00
Francesco Marano
6c8172c7cf Removed Time.parse('2000-01-01') expedient 2015-06-03 16:03:01 +02:00
Francesco Marano
ae5bae9899 Capitalised 'Last db update' in 'Last DB update' 2015-06-03 15:52:33 +02:00
Francesco Marano
b6bf306042 Removed unnecessary 'return' and '()' 2015-06-03 15:43:58 +02:00
Francesco Marano
9c5196dfec Added last db update to --version option (see #815) 2015-06-03 15:33:14 +02:00
Francesco Marano
3d7b8592ea Defined function to get last db update and removed redundant code 2015-06-03 15:32:34 +02:00
Christian Mehlmauer
e03f7691f2 switch to mitre 2015-05-24 09:02:26 +02:00
Christian Mehlmauer
7a54ac62d6 output path 2015-05-21 23:16:33 +02:00
Christian Mehlmauer
8db06d37d2 check if method exist 2015-05-16 08:21:32 +02:00
Christian Mehlmauer
5ee5e76544 new link types 2015-05-15 22:34:24 +02:00
Christian Mehlmauer
090cd999cb fix rspec 2015-05-12 22:36:07 +02:00
Christian Mehlmauer
50b75354e0 #796, do not swallow exit code 2015-05-12 21:51:15 +02:00
Christian Mehlmauer
c7b6b25851 removed debug output 2015-05-12 21:29:21 +02:00
Christian Mehlmauer
b931df654d fix #796 2015-05-12 21:28:12 +02:00
erwanlr
b5d5c4177d Removes potential spaces in robots.txt entries - Ref #819 2015-05-08 09:50:51 +01:00
Christian Mehlmauer
b22550ea55 fix #814 2015-05-01 22:15:58 +02:00
Christian Mehlmauer
04d50ebea5 more logic 2015-05-01 13:14:23 +02:00
Christian Mehlmauer
202180909c warn the user to update his DB files 2015-05-01 11:29:03 +02:00
erwanlr
0d806e6d74 Ignores potential non version chars in theme version detection - Fixes #816 2015-05-01 09:56:18 +01:00
erwanlr
54f31ebe7f Merge branch 'master' of github.com:wpscanteam/wpscan 2015-05-01 09:50:45 +01:00
erwanlr
227a39d2fa Updates the theme detection pattern - Ref #816 2015-05-01 09:50:20 +01:00
Christian Mehlmauer
99d8faa38b switch from gnutls to openssl 2015-04-30 23:45:10 +02:00
Christian Mehlmauer
9a7afe1549 option to hide banner 2015-04-30 21:39:03 +02:00
erwanlr
e6751e0d89 Remove potential new line at the end of .sha512 files during the update 2015-04-25 15:27:13 +01:00
ethicalhack3r
371f1df830 Remove www subdomain from wpvulndb.com link 2015-04-24 10:12:15 +02:00
Peter
8e1ba352ee Singular and plural sentences 2015-04-21 20:33:32 +02:00
ethicalhack3r
7ebfe42eb2 Install bundler gem README 2015-04-17 16:25:17 +02:00
ethicalhack3r
df514d3b9f Update to Ruby 2.2.2 2015-04-16 18:52:25 +02:00
erwanlr
acae16e7ee Adds the missing spec file - Ref #804 2015-04-15 18:38:57 +01:00
erwanlr
deb8508ea5 Updates the Theme detection pattern - Fixes #804 2015-04-15 18:37:23 +01:00
erwanlr
a4bbf41086 Forces UTF-8 encoding when enumerating usernames - Fixes #801 2015-04-11 12:26:15 +01:00
erwanlr
4fbc535b0c Increases default connect-timeout to 10s - Fixes #803 2015-04-10 16:58:21 +01:00
Ryan Dewhurst
36f6f98ce7 Merge pull request #802 from wpscanteam/remove_wpstoools
Remove wpstools #793
2015-04-10 14:29:57 +02:00
ethicalhack3r
21cc7d604c Remove wpstools #793 2015-04-10 13:43:11 +02:00
erwanlr
44207161e6 Also check for potential timed out requests when updating - Ref #797 2015-04-03 17:48:59 +01:00
erwanlr
dc20ef0754 Increases the timeout values - Ref #797 2015-04-03 17:10:07 +01:00
erwanlr
413ee7a6d3 Adds the HttpError exception - Fixes #792 2015-04-03 16:22:28 +01:00
Christian Mehlmauer
5b94714ca7 remove GHOST warning, fixes #795 2015-04-03 17:00:17 +02:00
Christian Mehlmauer
3675fe1ed7 whitespace 2015-04-03 16:45:41 +02:00
erwanlr
e074a03c40 Fixes Indentation 2015-04-03 12:29:27 +01:00
erwanlr
a7860f72a2 Merge pull request #798 from surfer190/master
Add db checksum to verbose logging during update
2015-04-03 12:25:16 +01:00
surfer190
4b587593ee Add db checksum to verbose logging during update 2015-04-03 10:27:26 +02:00
Christian Mehlmauer
0aa8a97070 additional output 2015-04-02 07:17:58 +02:00
Christian Mehlmauer
3c16f84853 even more output 2015-04-02 00:34:44 +02:00
Christian Mehlmauer
346898e549 more output 2015-04-02 00:21:53 +02:00
erwanlr
bcef4b2de7 Fixes #791 - Rogue character causing the scan of non-wordpress site to crash 2015-04-01 13:09:10 +01:00
erwanlr
e42bf7fd7c Consider the target down after 30 requests timed out requests instead of 10 - Fixes 790 2015-04-01 09:25:17 +01:00
Christian Mehlmauer
48cd0602d8 do not build gh-pages branch 2015-03-30 22:00:39 +02:00
Christian Mehlmauer
814e837ae5 No rdoc and no ri for gems 2015-03-30 21:58:28 +02:00
erwanlr
a58b34eba8 Updates request timeout values to realistic ones (and in seconds) 2015-03-30 16:08:49 +01:00
ethicalhack3r
7d790f8f79 Add blackarch to readme. Fix #789 2015-03-30 16:44:27 +02:00
63 changed files with 807 additions and 1063 deletions

View File

@@ -1 +1 @@
2.2.1 2.2.2

View File

@@ -11,6 +11,9 @@ rvm:
- 2.1.5 - 2.1.5
- 2.2.0 - 2.2.0
- 2.2.1 - 2.2.1
- 2.2.2
before_install:
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
script: bundle exec rspec script: bundle exec rspec
notifications: notifications:
email: email:
@@ -18,3 +21,7 @@ notifications:
matrix: matrix:
allow_failures: allow_failures:
- rvm: 1.9.2 - rvm: 1.9.2
# do not build gh-pages branch
branches:
except:
- gh-pages

View File

@@ -1,6 +1,52 @@
# Changelog # Changelog
## Master ## Master
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.7...master) [Work in progress](https://github.com/wpscanteam/wpscan/compare/2.8...master)
## Version 2.8
Released: 2015-06-22
New
* Warn the user to update his DB files
* Added last db update to --version option (see #815)
* Add db checksum to verbose logging during update
* Option to hide banner
* Continue if user chooses not to update + db exists
* Don't update if user chooses default + no DBs exist
* Updates request timeout values to realistic ones (and in seconds)
Removed
* Removed `Time.parse('2000-01-01')` expedient
* Removed unnecessary 'return' and '()'
* Removed debug output
* Removed wpstools
General core
* Update to Ruby 2.2.2
* Switch to mitre
* Install bundler gem README
* Switch from gnutls to openssl
Fixed issues
* Fix #789 - Add blackarch to readme
* Fix #790 - Consider the target down after 30 requests timed out requests instead of 10
* Fix #791 - Rogue character causing the scan of non-wordpress site to crash
* Fix #792 - Adds the HttpError exception
* Fix #795 - Remove GHOST warning
* Fix #796 - Do not swallow exit code
* Fix #797 - Increases the timeout values
* Fix #801 - Forces UTF-8 encoding when enumerating usernames
* Fix #803 - Increases default connect-timeout to 10s
* Fix #804 - Updates the Theme detection pattern
* Fix #816 - Ignores potential non version chars in theme version detection
* Fix #819 - Removes potential spaces in robots.txt entries
WPScan Database Statistics:
* Total vulnerable versions: 98
* Total vulnerable plugins: 1076
* Total vulnerable themes: 361
* Total version vulnerabilities: 1104
* Total plugin vulnerabilities: 1763
* Total theme vulnerabilities: 443
## Version 2.7 ## Version 2.7
Released: 2015-03-16 Released: 2015-03-16

View File

@@ -10,6 +10,6 @@ gem 'ruby-progressbar', '>=1.6.0'
group :test do group :test do
gem 'webmock', '>=1.17.2' gem 'webmock', '>=1.17.2'
gem 'simplecov' gem 'simplecov'
gem 'rspec', '>=3.0' gem 'rspec', '>= 3.3.0'
gem 'rspec-its' gem 'rspec-its'
end end

View File

@@ -89,10 +89,11 @@ WPScan comes pre-installed on the following Linux distributions:
- [Pentoo](http://www.pentoo.ch/) - [Pentoo](http://www.pentoo.ch/)
- [SamuraiWTF](http://samurai.inguardians.com/) - [SamuraiWTF](http://samurai.inguardians.com/)
- [ArchAssault](https://archassault.org/) - [ArchAssault](https://archassault.org/)
- [BlackArch](http://blackarch.org/)
Prerequisites: Prerequisites:
- Ruby >= 1.9.2 - Recommended: 2.2.1 - Ruby >= 1.9.2 - Recommended: 2.2.2
- Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault - Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
- RubyGems - Recommended: latest - RubyGems - Recommended: latest
- Git - Git
@@ -104,21 +105,21 @@ If installed from Github update the code base with ```git pull```. The databases
Before Ubuntu 14.04: Before Ubuntu 14.04:
sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev sudo apt-get install libcurl4-openssl-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
git clone https://github.com/wpscanteam/wpscan.git git clone https://github.com/wpscanteam/wpscan.git
cd wpscan cd wpscan
sudo gem install bundler && bundle install --without test sudo gem install bundler && bundle install --without test
From Ubuntu 14.04: From Ubuntu 14.04:
sudo apt-get install libcurl4-gnutls-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential sudo apt-get install libcurl4-openssl-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential
git clone https://github.com/wpscanteam/wpscan.git git clone https://github.com/wpscanteam/wpscan.git
cd wpscan cd wpscan
sudo gem install bundler && bundle install --without test sudo gem install bundler && bundle install --without test
####Installing on Debian: ####Installing on Debian:
sudo apt-get install git ruby ruby-dev libcurl4-gnutls-dev make sudo apt-get install git ruby ruby-dev libcurl4-openssl-dev make
git clone https://github.com/wpscanteam/wpscan.git git clone https://github.com/wpscanteam/wpscan.git
cd wpscan cd wpscan
sudo gem install bundler sudo gem install bundler
@@ -155,12 +156,13 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
curl -sSL https://get.rvm.io | bash -s stable curl -sSL https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm source ~/.rvm/scripts/rvm
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
rvm install 2.2.1 rvm install 2.2.2
rvm use 2.2.1 --default rvm use 2.2.2 --default
echo "gem: --no-ri --no-rdoc" > ~/.gemrc echo "gem: --no-ri --no-rdoc" > ~/.gemrc
gem install bundler gem install bundler
git clone https://github.com/wpscanteam/wpscan.git git clone https://github.com/wpscanteam/wpscan.git
cd wpscan cd wpscan
gem install bundler
bundle install --without test bundle install --without test
#### KNOWN ISSUES #### KNOWN ISSUES
@@ -310,28 +312,13 @@ Debug output...
```ruby wpscan.rb --url www.example.com --debug-output 2>debug.log``` ```ruby wpscan.rb --url www.example.com --debug-output 2>debug.log```
#### WPSTOOLS ARGUMENTS
-v, --verbose Verbose output
--check-vuln-ref-urls, --cvru Check all the vulnerabilities reference urls for 404
--check-local-vulnerable-files, --clvf LOCAL_DIRECTORY Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells
-s, --stats Show WpScan Database statistics.
--spellcheck, --sc Check all files for common spelling mistakes.
#### WPSTOOLS EXAMPLES
Locally scan a wordpress installation for vulnerable files or shells:
```ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/```
#### PROJECT HOME #### PROJECT HOME
[http://www.wpscan.org](http://www.wpscan.org) [http://www.wpscan.org](http://www.wpscan.org)
#### VULNERABILITY DATABASE #### VULNERABILITY DATABASE
[https://www.wpvulndb.com](https://www.wpvulndb.com) [https://wpvulndb.com](https://wpvulndb.com)
#### GIT REPOSITORY #### GIT REPOSITORY

BIN
data.zip

Binary file not shown.

View File

@@ -23,7 +23,7 @@ end
html = open(html_path).read html = open(html_path).read
examples = html.match(/(\d+) examples/)[0].to_i rescue 0 examples = html.match(/(\d+) examples/)[0].to_i rescue 0
errors = html.match(/(\d+) errors/)[0].to_i rescue 0 errors = html.match(/(\d+) errors/)[0].to_i rescue 0
if errors == 0 then if errors == 0
errors = html.match(/(\d+) failure/)[0].to_i rescue 0 errors = html.match(/(\d+) failure/)[0].to_i rescue 0
end end
pending = html.match(/(\d+) pending/)[0].to_i rescue 0 pending = html.match(/(\d+) pending/)[0].to_i rescue 0

View File

@@ -10,9 +10,9 @@
"cache_ttl": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled "cache_ttl": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
"request_timeout": 2000, // 2s "request_timeout": 60, // 1min
"connect_timeout": 1000, // 1s "connect_timeout": 10, // 10s
"max_threads": 20 "max_threads": 20
} }

View File

@@ -73,10 +73,8 @@ class Browser
@max_threads = 20 @max_threads = 20
# 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled # 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
@cache_ttl = 600 @cache_ttl = 600
# 2s @request_timeout = 60 # 60s
@request_timeout = 2000 @connect_timeout = 10 # 10s
# 1s
@connect_timeout = 1000
@user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)" @user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
end end

View File

@@ -14,7 +14,7 @@ class WpItems < Array
self.wp_target = wp_target self.wp_target = wp_target
end end
# @param [String] argv # @param [String] args
# #
# @return [ void ] # @return [ void ]
def add(*args) def add(*args)

View File

@@ -32,7 +32,7 @@ class WpItems < Array
progress_bar.progress += 1 if options[:show_progression] progress_bar.progress += 1 if options[:show_progression]
if target_item.exists?(exist_options, response) if target_item.exists?(exist_options, response)
if !results.include?(target_item) unless results.include?(target_item)
if !options[:only_vulnerable] || options[:only_vulnerable] && target_item.vulnerable? if !options[:only_vulnerable] || options[:only_vulnerable] && target_item.vulnerable?
results << target_item results << target_item
end end

View File

@@ -6,7 +6,6 @@ DATA_DIR = File.join(ROOT_DIR, 'data')
CONF_DIR = File.join(ROOT_DIR, 'conf') CONF_DIR = File.join(ROOT_DIR, 'conf')
CACHE_DIR = File.join(ROOT_DIR, 'cache') CACHE_DIR = File.join(ROOT_DIR, 'cache')
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan') WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan')
WPSTOOLS_LIB_DIR = File.join(LIB_DIR, 'wpstools')
UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater') UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater')
COMMON_LIB_DIR = File.join(LIB_DIR, 'common') COMMON_LIB_DIR = File.join(LIB_DIR, 'common')
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models') MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models')
@@ -17,7 +16,6 @@ LOG_FILE = File.join(ROOT_DIR, 'log.txt')
# Plugins directories # Plugins directories
COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins') COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins')
WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM
WPSTOOLS_PLUGINS_DIR = File.join(WPSTOOLS_LIB_DIR, 'plugins')
# Data files # Data files
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.txt') PLUGINS_FILE = File.join(DATA_DIR, 'plugins.txt')
@@ -33,8 +31,9 @@ LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml')
WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd') WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd')
LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd') LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd')
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt') USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update')
WPSCAN_VERSION = '2.7' WPSCAN_VERSION = '2.8'
$LOAD_PATH.unshift(LIB_DIR) $LOAD_PATH.unshift(LIB_DIR)
$LOAD_PATH.unshift(WPSCAN_LIB_DIR) $LOAD_PATH.unshift(WPSCAN_LIB_DIR)
@@ -42,7 +41,7 @@ $LOAD_PATH.unshift(MODELS_LIB_DIR)
def kali_linux? def kali_linux?
begin begin
File.readlines("/etc/debian_version").grep(/^kali/i).any? File.readlines('/etc/debian_version').grep(/^kali/i).any?
rescue rescue
false false
end end
@@ -55,7 +54,7 @@ def require_files_from_directory(absolute_dir_path, files_pattern = '*.rb')
files = Dir[File.join(absolute_dir_path, files_pattern)] files = Dir[File.join(absolute_dir_path, files_pattern)]
# Files in the root dir are loaded first, then those in the subdirectories # Files in the root dir are loaded first, then those in the subdirectories
files.sort_by { |file| [file.count("/"), file] }.each do |f| files.sort_by { |file| [file.count('/'), file] }.each do |f|
f = File.expand_path(f) f = File.expand_path(f)
#puts "require #{f}" # Used for debug #puts "require #{f}" # Used for debug
require f require f
@@ -80,6 +79,20 @@ def missing_db_file?
false false
end end
def last_update
date = nil
if File.exists?(LAST_UPDATE_FILE)
content = File.read(LAST_UPDATE_FILE)
date = Time.parse(content) rescue nil
end
date
end
def update_required?
date = last_update
(true if date.nil?) or (date < 5.days.ago)
end
# Define colors # Define colors
def colorize(text, color_code) def colorize(text, color_code)
if $COLORSWITCH if $COLORSWITCH
@@ -110,19 +123,21 @@ def blue(text)
end end
def critical(text) def critical(text)
red(text) $exit_code += 1 if defined?($exit_code) # hack for undefined var via rspec
"#{red('[!]')} #{text}"
end end
def warning(text) def warning(text)
amber(text) $exit_code += 1 if defined?($exit_code) # hack for undefined var via rspec
"#{amber('[!]')} #{text}"
end end
def info(text) def info(text)
green(text) "#{green('[+]')} #{text}"
end end
def notice(text) def notice(text)
blue(text) "#{blue('[i]')} #{text}"
end end
# our 1337 banner # our 1337 banner

View File

@@ -36,8 +36,8 @@ class DbUpdater
url = "#{remote_file_url(filename)}.sha512" url = "#{remote_file_url(filename)}.sha512"
res = Browser.get(url, request_params) res = Browser.get(url, request_params)
fail "Unable to get #{url}" unless res.code == 200 fail DownloadError, res if res.timed_out? || res.code != 200
res.body res.body.chomp
end end
def local_file_path(filename) def local_file_path(filename)
@@ -72,7 +72,7 @@ class DbUpdater
file_url = remote_file_url(filename) file_url = remote_file_url(filename)
res = Browser.get(file_url, request_params) res = Browser.get(file_url, request_params)
fail "Error while downloading #{file_url}" unless res.code == 200 fail DownloadError, res if res.timed_out? || res.code != 200
File.open(file_path, 'wb') { |f| f.write(res.body) } File.open(file_path, 'wb') { |f| f.write(res.body) }
local_file_checksum(filename) local_file_checksum(filename)
@@ -96,6 +96,7 @@ class DbUpdater
puts ' [i] Downloading new file' if verbose puts ' [i] Downloading new file' if verbose
dl_checksum = download(filename) dl_checksum = download(filename)
puts " [i] Downloaded File Checksum: #{dl_checksum}" if verbose puts " [i] Downloaded File Checksum: #{dl_checksum}" if verbose
puts " [i] Database File Checksum : #{db_checksum}" if verbose
unless dl_checksum == db_checksum unless dl_checksum == db_checksum
fail "#{filename}: checksums do not match" fail "#{filename}: checksums do not match"
@@ -111,5 +112,8 @@ class DbUpdater
end end
end end
end end
# write last_update date to file
File.write(LAST_UPDATE_FILE, Time.now)
end end
end end

33
lib/common/errors.rb Normal file
View File

@@ -0,0 +1,33 @@
# HTTP Error
class HttpError < StandardError
attr_reader :response
# @param [ Typhoeus::Response ] response
def initialize(response)
@response = response
end
def failure_details
msg = response.effective_url
if response.code == 0 || response.timed_out?
msg += " (#{response.return_message})"
else
msg += " (status: #{response.code})"
end
msg
end
def message
"HTTP Error: #{failure_details}"
end
end
# Used in the Updater
class DownloadError < HttpError
def message
"Unable to get #{failure_details}"
end
end

View File

@@ -78,7 +78,7 @@ module Terminal
class Style class Style
@@defaults = { @@defaults = {
:border_x => "-", :border_y => "|", :border_i => "+", :border_x => '-', :border_y => '|', :border_i => '+',
:padding_left => 1, :padding_right => 1, :padding_left => 1, :padding_right => 1,
:margin_left => '', :margin_left => '',
:width => nil, :alignment => nil :width => nil, :alignment => nil
@@ -102,7 +102,20 @@ class Numeric
def bytes_to_human def bytes_to_human
units = %w{B KB MB GB TB} units = %w{B KB MB GB TB}
e = (Math.log(self)/Math.log(1024)).floor e = (Math.log(self)/Math.log(1024)).floor
s = "%.3f" % (to_f / 1024**e) s = '%.3f' % (to_f / 1024**e)
s.sub(/\.?0*$/, ' ' + units[e]) s.sub(/\.?0*$/, ' ' + units[e])
end end
end end
# time calculations
class Fixnum
SECONDS_IN_DAY = 24 * 60 * 60
def days
self * SECONDS_IN_DAY
end
def ago
Time.now - self
end
end

View File

@@ -6,16 +6,17 @@ class Vulnerability
# output the vulnerability # output the vulnerability
def output(verbose = false) def output(verbose = false)
puts puts
puts "#{critical('[!]')} Title: #{title}" puts critical("Title: #{title}")
references.each do |key, urls| references.each do |key, urls|
methodname = "url_#{key}" methodname = "url_#{key}"
urls.each do |u| urls.each do |u|
next unless respond_to?(methodname)
url = send(methodname, u) url = send(methodname, u)
puts " Reference: #{url}" if url puts " Reference: #{url}" if url
end end
end end
if !fixed_in.nil? unless fixed_in.nil?
puts "#{notice('[i]')} Fixed in: #{fixed_in}" puts notice("Fixed in: #{fixed_in}")
end end
end end
end end

View File

@@ -6,31 +6,39 @@ class Vulnerability
def url_metasploit(module_path) def url_metasploit(module_path)
# remove leading slash # remove leading slash
module_path = module_path.sub(/^\//, '') module_path = module_path.sub(/^\//, '')
"http://www.rapid7.com/db/modules/#{module_path}" "https://www.rapid7.com/db/modules/#{module_path}"
end end
def url_url(url) def url_url(url)
url url
end end
def url_cve(cve) def url_cve(id)
"http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-#{cve}" "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-#{id}"
end end
def url_osvdb(id) def url_osvdb(id)
"http://osvdb.org/#{id}" "http://osvdb.org/show/osvdb/#{id}"
end end
def url_secunia(id) def url_secunia(id)
"https://secunia.com/advisories/#{id}" "https://secunia.com/advisories/#{id}/"
end end
def url_exploitdb(id) def url_exploitdb(id)
"http://www.exploit-db.com/exploits/#{id}/" "https://www.exploit-db.com/exploits/#{id}/"
end end
def url_id(id) def url_id(id)
"https://wpvulndb.com/vulnerabilities/#{id}" "https://wpvulndb.com/vulnerabilities/#{id}"
end end
def url_packetstorm(id)
"http://packetstormsecurity.com/files/#{id}/"
end
def url_securityfocus(id)
"http://www.securityfocus.com/bid/#{id}/"
end
end end
end end

View File

@@ -6,19 +6,19 @@ class WpItem
# @return [ Void ] # @return [ Void ]
def output(verbose = false) def output(verbose = false)
puts puts
puts "#{info('[+]')} Name: #{self}" #this will also output the version number if detected puts info("Name: #{self}") #this will also output the version number if detected
puts " | Location: #{url}" puts " | Location: #{url}"
#puts " | WordPress: #{wordpress_url}" if wordpress_org_item? #puts " | WordPress: #{wordpress_url}" if wordpress_org_item?
puts " | Readme: #{readme_url}" if has_readme? puts " | Readme: #{readme_url}" if has_readme?
puts " | Changelog: #{changelog_url}" if has_changelog? puts " | Changelog: #{changelog_url}" if has_changelog?
puts "#{warning('[!]')} Directory listing is enabled: #{url}" if has_directory_listing? puts warning("Directory listing is enabled: #{url}") if has_directory_listing?
puts "#{warning('[!]')} An error_log file has been found: #{error_log_url}" if has_error_log? puts warning("An error_log file has been found: #{error_log_url}") if has_error_log?
additional_output(verbose) if respond_to?(:additional_output) additional_output(verbose) if respond_to?(:additional_output)
if version.nil? && vulnerabilities.length > 0 if version.nil? && vulnerabilities.length > 0
puts puts
puts "#{warning('[+]')} We could not determine a version so all vulnerabilities are printed out" puts warning('We could not determine a version so all vulnerabilities are printed out')
end end
vulnerabilities.output vulnerabilities.output

View File

@@ -22,7 +22,7 @@ class WpItem
# @return [ String ] # @return [ String ]
def to_s def to_s
item_version = self.version item_version = self.version
"#@name#{' - v' + item_version.strip if item_version}" "#{@name}#{' - v' + item_version.strip if item_version}"
end end
# Extracts the version number from a given string/body # Extracts the version number from a given string/body

View File

@@ -14,7 +14,7 @@ class WpTheme < WpItem
def get_parent_theme_style_url def get_parent_theme_style_url
if is_child_theme? if is_child_theme?
return style_url.sub("/#{name}/style.css", "/#@theme_template/style.css") return style_url.sub("/#{name}/style.css", "/#{@theme_template}/style.css")
end end
nil nil
end end

View File

@@ -30,18 +30,15 @@ class WpTheme < WpItem
response = Browser.get_and_follow_location(target_uri.to_s) response = Browser.get_and_follow_location(target_uri.to_s)
# https + domain is optional because of relative links # https + domain is optional because of relative links
matches = /(?:https?:\/\/[^"']+)?\/([^\/]+)\/themes\/([^"'\/]+)[^"']*\/style.css/i.match(response.body) return unless response.body =~ %r{(?:https?://[^"']+/)?([^/\s]+)/themes/([^"'/]+)[^"']*/style.css}i
if matches
return new( new(
target_uri, target_uri,
{ name: Regexp.last_match[2],
name: matches[2], referenced_url: Regexp.last_match[0],
referenced_url: matches[0], wp_content_dir: Regexp.last_match[1]
wp_content_dir: matches[1]
}
) )
end end
end
# @param [ URI ] target_uri # @param [ URI ] target_uri
# #
@@ -50,7 +47,6 @@ class WpTheme < WpItem
body = Browser.get(target_uri.to_s).body body = Browser.get(target_uri.to_s).body
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />} regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
if matches = regexp.match(body) if matches = regexp.match(body)
woo_theme_name = matches[1] woo_theme_name = matches[1]
woo_theme_version = matches[2] woo_theme_version = matches[2]
@@ -58,10 +54,8 @@ class WpTheme < WpItem
return new( return new(
target_uri, target_uri,
{
name: woo_theme_name, name: woo_theme_name,
version: woo_theme_version version: woo_theme_version
}
) )
end end
end end

View File

@@ -10,16 +10,16 @@ class WpTheme
theme_desc = verbose ? @theme_description : truncate(@theme_description, 100) theme_desc = verbose ? @theme_description : truncate(@theme_description, 100)
puts " | Style URL: #{style_url}" puts " | Style URL: #{style_url}"
puts " | Referenced style.css: #{referenced_url}" if referenced_url && referenced_url != style_url puts " | Referenced style.css: #{referenced_url}" if referenced_url && referenced_url != style_url
puts " | Theme Name: #@theme_name" if @theme_name puts " | Theme Name: #{@theme_name}" if @theme_name
puts " | Theme URI: #@theme_uri" if @theme_uri puts " | Theme URI: #{@theme_uri}" if @theme_uri
puts " | Description: #{theme_desc}" puts " | Description: #{theme_desc}"
puts " | Author: #@theme_author" if @theme_author puts " | Author: #{@theme_author}" if @theme_author
puts " | Author URI: #@theme_author_uri" if @theme_author_uri puts " | Author URI: #{@theme_author_uri}" if @theme_author_uri
puts " | Template: #@theme_template" if @theme_template and verbose puts " | Template: #{@theme_template}" if @theme_template and verbose
puts " | License: #@theme_license" if @theme_license and verbose puts " | License: #{@theme_license}" if @theme_license and verbose
puts " | License URI: #@theme_license_uri" if @theme_license_uri and verbose puts " | License URI: #{@theme_license_uri}" if @theme_license_uri and verbose
puts " | Tags: #@theme_tags" if @theme_tags and verbose puts " | Tags: #{@theme_tags}" if @theme_tags and verbose
puts " | Text Domain: #@theme_text_domain" if @theme_text_domain and verbose puts " | Text Domain: #{@theme_text_domain}" if @theme_text_domain and verbose
end end
end end

View File

@@ -3,7 +3,7 @@
class WpTheme < WpItem class WpTheme < WpItem
module Versionable module Versionable
def version def version
@version ||= Browser.get(style_url).body[%r{Version:\s*([^\s]+)}i, 1] @version ||= Browser.get(style_url).body[%r{Version:\s*(?!trunk)([0-9a-z\.-]+)}i, 1]
end end
end end
end end

View File

@@ -5,7 +5,7 @@ class WpTimthumb < WpItem
def output(verbose = false) def output(verbose = false)
puts puts
puts "#{info('[+]')} #{self}" #this will also output the version number if detected puts info("#{self}") #this will also output the version number if detected
vulnerabilities.output vulnerabilities.output
end end

View File

@@ -15,7 +15,7 @@ class WpTimthumb < WpItem
end end
def check_rce_132 def check_rce_132
return rce_132_vuln unless VersionCompare.lesser_or_equal?('1.33', version) rce_132_vuln unless VersionCompare.lesser_or_equal?('1.33', version)
end end
# Vulnerable versions : > 1.35 (or >= 2.0) and < 2.8.14 # Vulnerable versions : > 1.35 (or >= 2.0) and < 2.8.14
@@ -24,7 +24,7 @@ class WpTimthumb < WpItem
response = Browser.get(uri.merge('?webshot=1&src=http://' + default_allowed_domains.sample)) response = Browser.get(uri.merge('?webshot=1&src=http://' + default_allowed_domains.sample))
return rce_webshot_vuln unless response.body =~ /WEBSHOT_ENABLED == true/ rce_webshot_vuln unless response.body =~ /WEBSHOT_ENABLED == true/
end end
# @return [ Array<String> ] The default allowed domains (between the 2.0 and 2.8.13) # @return [ Array<String> ] The default allowed domains (between the 2.0 and 2.8.13)

View File

@@ -15,7 +15,7 @@ class WpUser < WpItem
# @return [ URI ] The uri to the author page # @return [ URI ] The uri to the author page
def uri def uri
if id if id
return @uri.merge("?author=#{id}") @uri.merge("?author=#{id}")
else else
raise 'The id is nil' raise 'The id is nil'
end end

View File

@@ -34,7 +34,7 @@ class WpUser < WpItem
# Generate a random one on each request # Generate a random one on each request
unless redirect_url unless redirect_url
random = (0...8).map { 65.+(rand(26)).chr }.join random = (0...8).map { 65.+(rand(26)).chr }.join
redirect_url = "#@uri#{random}/" redirect_url = "#{@uri}#{random}/"
end end
request = login_request(password, redirect_url) request = login_request(password, redirect_url)
@@ -66,7 +66,7 @@ class WpUser < WpItem
puts if options[:show_progression] # mandatory to avoid the output of the progressbar to be overriden puts if options[:show_progression] # mandatory to avoid the output of the progressbar to be overriden
end end
# @param [ Integer ] targets_size # @param [ Integer ] passwords_size
# @param [ Hash ] options # @param [ Hash ] options
# #
# @return [ ProgressBar ] # @return [ ProgressBar ]
@@ -109,13 +109,13 @@ class WpUser < WpItem
elsif response.body =~ /login_error/i elsif response.body =~ /login_error/i
verbose = "\n Incorrect login and/or password." verbose = "\n Incorrect login and/or password."
elsif response.timed_out? elsif response.timed_out?
progression = "#{critical('ERROR:')} Request timed out." progression = critical('ERROR: Request timed out.')
elsif response.code == 0 elsif response.code == 0
progression = "#{critical('ERROR:')} No response from remote server. WAF/IPS?" progression = critical("ERROR: No response from remote server. WAF/IPS? (#{response.return_message})")
elsif response.code.to_s =~ /^50/ elsif response.code.to_s =~ /^50/
progression = "#{critical('ERROR:')} Server error, try reducing the number of threads." progression = critical('ERROR: Server error, try reducing the number of threads.')
else else
progression = "#{critical('ERROR:')} We received an unknown response for #{password}..." progression = critical("ERROR: We received an unknown response for #{password}...")
verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n") verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
end end

View File

@@ -39,7 +39,9 @@ class WpUser < WpItem
# #
# @return [ String ] The login # @return [ String ] The login
def self.login_from_author_pattern(text) def self.login_from_author_pattern(text)
text[%r{/author/([^/\b]+)/?}i, 1] return unless text =~ %r{/author/([^/\b]+)/?}i
Regexp.last_match[1].force_encoding('UTF-8')
end end
# @param [ String ] body # @param [ String ] body
@@ -52,6 +54,7 @@ class WpUser < WpItem
unless login unless login
# No Permalinks # No Permalinks
login = body[%r{<body class="archive author author-([^\s]+)[ "]}i, 1] login = body[%r{<body class="archive author author-([^\s]+)[ "]}i, 1]
login ? login.force_encoding('UTF-8') : nil
end end
login login

View File

@@ -5,13 +5,16 @@ class WpVersion < WpItem
def output(verbose = false) def output(verbose = false)
puts puts
puts "#{info('[+]')} WordPress version #{self.number} identified from #{self.found_from}" puts info("WordPress version #{self.number} identified from #{self.found_from}")
vulnerabilities = self.vulnerabilities vulnerabilities = self.vulnerabilities
unless vulnerabilities.empty? unless vulnerabilities.empty?
puts "#{critical('[!]')} #{vulnerabilities.size} vulnerabilities identified from the version number" if vulnerabilities.size == 1
puts critical("#{vulnerabilities.size} vulnerability identified from the version number")
else
puts critical("#{vulnerabilities.size} vulnerabilities identified from the version number")
end
vulnerabilities.output vulnerabilities.output
end end
end end

View File

@@ -54,10 +54,7 @@ class WebSite
redirected_uri = URI.parse(add_trailing_slash(add_http_protocol(url))) redirected_uri = URI.parse(add_trailing_slash(add_http_protocol(url)))
if response.code == 301 || response.code == 302 if response.code == 301 || response.code == 302
redirection = response.headers_hash['location'] redirection = redirected_uri.merge(response.headers_hash['location']).to_s
if redirection[0] == '/'
redirection = "#{redirected_uri.scheme}://#{redirected_uri.host}#{redirection}"
end
return redirection if url == redirection # prevents infinite loop return redirection if url == redirection # prevents infinite loop

View File

@@ -15,7 +15,6 @@ class WebSite
@uri.clone.merge('robots.txt').to_s @uri.clone.merge('robots.txt').to_s
end end
# Parse robots.txt # Parse robots.txt
# @return [ Array ] URLs generated from robots.txt # @return [ Array ] URLs generated from robots.txt
def parse_robots_txt def parse_robots_txt
@@ -40,9 +39,9 @@ class WebSite
entries.each do |d| entries.each do |d|
begin begin
temp = @uri.clone temp = @uri.clone
temp.path = d temp.path = d.strip
rescue URI::Error rescue URI::Error
temp = d temp = d.strip
end end
return_object << temp.to_s return_object << temp.to_s
end end

View File

@@ -47,7 +47,7 @@ class WpTarget < WebSite
if wp_content_dir if wp_content_dir
dir = wp_content_dir dir = wp_content_dir
else else
dir = 'wp-content' dir = 'wp-content'
end end
if response.body =~ /["'][^"']*\/#{Regexp.escape(dir)}\/[^"']*["']/i if response.body =~ /["'][^"']*\/#{Regexp.escape(dir)}\/[^"']*["']/i

View File

@@ -7,10 +7,15 @@ class WpTarget < WebSite
# #
# @return [ Boolean ] # @return [ Boolean ]
def has_full_path_disclosure? def has_full_path_disclosure?
response = Browser.get(full_path_disclosure_url()) response = Browser.get(full_path_disclosure_url)
response.body[%r{Fatal error}i] ? true : false response.body[%r{Fatal error}i] ? true : false
end end
def full_path_disclosure_data
return nil unless has_full_path_disclosure?
Browser.get(full_path_disclosure_url).body[%r{<b>([^<]+\.php)</b>}, 1]
end
# @return [ String ] # @return [ String ]
def full_path_disclosure_url def full_path_disclosure_url
@uri.merge('wp-includes/rss-functions.php').to_s @uri.merge('wp-includes/rss-functions.php').to_s

View File

@@ -8,7 +8,7 @@ class WpTarget < WebSite
@login_protection_plugin = nil @login_protection_plugin = nil
def has_login_protection? def has_login_protection?
!login_protection_plugin().nil? !login_protection_plugin.nil?
end end
# Checks if a login protection plugin is enabled # Checks if a login protection plugin is enabled
@@ -74,7 +74,7 @@ class WpTarget < WebSite
# http://wordpress.org/extend/plugins/login-security-solution/ # http://wordpress.org/extend/plugins/login-security-solution/
def has_login_security_solution_protection? def has_login_security_solution_protection?
Browser.get(login_security_solution_url()).code != 404 Browser.get(login_security_solution_url).code != 404
end end
def login_security_solution_url def login_security_solution_url

View File

@@ -10,7 +10,7 @@ class WpTarget < WebSite
# #
# @return [ Boolean ] # @return [ Boolean ]
def has_readme? def has_readme?
response = Browser.get(readme_url()) response = Browser.get(readme_url)
unless response.code == 404 unless response.code == 404
return response.body =~ %r{wordpress}i ? true : false return response.body =~ %r{wordpress}i ? true : false

View File

@@ -113,7 +113,7 @@ end
# Hook to check if the target if down during the scan # Hook to check if the target if down during the scan
# And have the number of requests performed to display at the end of the scan # And have the number of requests performed to display at the end of the scan
# The target is considered down after 10 requests with status = 0 # The target is considered down after 30 requests with status = 0
down = 0 down = 0
@total_requests_done = 0 @total_requests_done = 0
@@ -121,5 +121,5 @@ Typhoeus.on_complete do |response|
down += 1 if response.code == 0 down += 1 if response.code == 0
@total_requests_done += 1 @total_requests_done += 1
fail 'The target seems to be down' if down >= 10 fail 'The target seems to be down' if down >= 30
end end

View File

@@ -41,7 +41,8 @@ class WpscanOptions
:cache_ttl, :cache_ttl,
:request_timeout, :request_timeout,
:connect_timeout, :connect_timeout,
:max_threads :max_threads,
:no_banner
] ]
attr_accessor *ACCESSOR_OPTIONS attr_accessor *ACCESSOR_OPTIONS
@@ -273,7 +274,8 @@ class WpscanOptions
['--batch', GetoptLong::NO_ARGUMENT], ['--batch', GetoptLong::NO_ARGUMENT],
['--no-color', GetoptLong::NO_ARGUMENT], ['--no-color', GetoptLong::NO_ARGUMENT],
['--cookie', GetoptLong::REQUIRED_ARGUMENT], ['--cookie', GetoptLong::REQUIRED_ARGUMENT],
['--log', GetoptLong::NO_ARGUMENT] ['--log', GetoptLong::NO_ARGUMENT],
['--no-banner', GetoptLong::NO_ARGUMENT]
) )
end end

View File

@@ -1,138 +0,0 @@
# encoding: UTF-8
class CheckerPlugin < Plugin
def initialize
super(author: 'WPScanTeam - @erwanlr')
register_options(
['--check-vuln-ref-urls', '--cvru', 'Check all the vulnerabilities reference urls for 404'],
['--check-local-vulnerable-files LOCAL_DIRECTORY', '--clvf', 'Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells']
)
end
def run(options = {})
if options[:check_vuln_ref_urls]
check_vuln_ref_urls
end
if options[:check_local_vulnerable_files]
check_local_vulnerable_files(options[:check_local_vulnerable_files])
end
end
def check_vuln_ref_urls
vuln_ref_files = [PLUGINS_VULNS_FILE, THEMES_VULNS_FILE, WP_VULNS_FILE]
error_codes = [404, 500, 403]
not_found_regexp = %r{No Results Found|error 404|ID Invalid or Not Found}i
puts '[+] Checking vulnerabilities reference urls'
vuln_ref_files.each do |vuln_ref_file|
json = json(vuln_ref_file)
urls = []
json.each do |asset|
asset[asset.keys.inject]['vulnerabilities'].each do |url|
unless url['url'].nil?
url['url'].each do |url|
urls << url
end
end
end
end
urls.uniq!
puts "[!] No URLs found in #{vuln_ref_file}!" if urls.empty?
dead_urls = []
queue_count = 0
request_count = 0
browser = Browser.instance
hydra = browser.hydra
number_of_urls = urls.size
urls.each do |url|
request = browser.forge_request(url, { cache_ttl: 0, followlocation: true })
request_count += 1
request.on_complete do |response|
print "\r [+] Checking #{vuln_ref_file} #{number_of_urls} total ... #{(request_count * 100) / number_of_urls}% complete."
if error_codes.include?(response.code) or not_found_regexp.match(response.body)
dead_urls << url
end
end
hydra.queue(request)
queue_count += 1
if queue_count == browser.max_threads
hydra.run
queue_count = 0
end
end
hydra.run
puts
unless dead_urls.empty?
dead_urls.each { |url| puts " Not Found #{url}" }
end
end
end
def check_local_vulnerable_files(dir_to_scan)
if Dir.exist?(dir_to_scan)
xml_file = LOCAL_FILES_FILE
local_hashes = {}
file_extension_to_scan = '*.{js,php,swf,html,htm}'
print '[+] Generating local hashes ... '
Dir[File.join(dir_to_scan, '**', file_extension_to_scan)]
.select { |f| File.file?(f) }
.each do |filename|
sha1sum = Digest::SHA1.file(filename).hexdigest
if local_hashes.key?(sha1sum)
local_hashes[sha1sum] << filename
else
local_hashes[sha1sum] = [filename]
end
end
puts 'done.'
puts '[+] Checking for vulnerable files ...'
xml = xml(xml_file)
xml.xpath('//hash').each do |node|
sha1sum = node.attribute('sha1').text
if local_hashes.has_key?(sha1sum)
local_filenames = local_hashes[sha1sum]
vuln_title = node.search('title').text
vuln_filename = node.search('file').text
vuln_refrence = node.search('reference').text
puts " #{vuln_filename} found :"
puts ' | Location(s):'
local_filenames.each do |file|
puts " | - #{file}"
end
puts ' |'
puts " | Title: #{vuln_title}"
puts " | Refrence: #{vuln_refrence}" if !vuln_refrence.empty?
puts
end
end
puts 'done.'
else
puts "The supplied directory '#{dir_to_scan}' does not exist"
end
end
end

View File

@@ -1,91 +0,0 @@
# encoding: UTF-8
class CheckerSpelling < Plugin
def initialize
super(author: 'WPScanTeam - @ethicalhack3r')
register_options(['--spellcheck', '--sc', 'Check all files for common spelling mistakes.'])
end
def run(options = {})
spellcheck if options[:spellcheck]
end
def spellcheck
mistakes = 0
puts '[+] Checking for spelling mistakes'
puts
files.each do |file_name|
if File.exists?(file_name)
file = File.open(file_name, 'r')
misspellings.each_key do |misspelling|
begin
file.read.scan(/#{misspelling}/).each do |match|
mistakes += 1
puts "[MISSPELLING] File: #{file_name} Bad: #{match} Good: #{misspellings[misspelling]}"
end
rescue => e
puts "Error in #{file_name} #{e}"
next
end
end
file.close
end
end
puts
puts "[+] Found #{mistakes} spelling mistakes"
mistakes
end
def misspellings
{
/databse/i => 'database',
/whith/i => 'with',
/wich/i => 'which',
/verions/i => 'versions',
/vulnerabilitiy/i => 'vulnerability',
/unkown/i => 'unknown',
/recieved/i => 'received',
/acheive/i => 'achieve',
/wierd/i => 'weird',
/untill/i => 'until',
/alot/i => 'a lot',
/randomstorm/ => 'RandomStorm',
/wpscan/ => 'WPScan',
/Wordpress/ => 'WordPress'
}
end
def files
files = Dir['**/*'].reject {|fn| File.directory?(fn) }
ignore.each do |ignore|
files.delete_if { |data| data.match(ignore) }
end
files
end
def ignore
ignore = []
ignore << File.basename(__FILE__)
ignore << 'spec/cache/'
ignore << 'spec/spec_session/'
ignore << 'cache/'
ignore << 'coverage/'
ignore << 'wordlist-iso-8859-1'
ignore << 'log.txt'
ignore << 'debug.log'
ignore << 'wordlist.txt'
ignore
end
end

View File

@@ -1,106 +0,0 @@
# encoding: UTF-8
class StatsPlugin < Plugin
def initialize
super(author: 'WPScanTeam - Christian Mehlmauer')
register_options(
['--stats', '-s', 'Show WpScan Database statistics.']
)
end
def run(options = {})
if options[:stats]
date_wp = File.mtime(WP_VULNS_FILE)
date_plugins = File.mtime(PLUGINS_VULNS_FILE)
date_themes = File.mtime(THEMES_VULNS_FILE)
date_plugins_full = File.mtime(PLUGINS_FULL_FILE)
date_themes_full = File.mtime(THEMES_FULL_FILE)
puts "WPScan Database Statistics:"
puts "---------------------------"
puts
puts "[#] Total vulnerable versions: #{vuln_core_count}"
puts "[#] Total vulnerable plugins: #{vuln_plugin_count}"
puts "[#] Total vulnerable themes: #{vuln_theme_count}"
puts
puts "[#] Total version vulnerabilities: #{version_vulns_count}"
puts "[#] Total fixed vulnerabilities: #{fix_version_count}"
puts
puts "[#] Total plugin vulnerabilities: #{plugin_vulns_count}"
puts "[#] Total fixed vulnerabilities: #{fix_plugin_count}"
puts
puts "[#] Total theme vulnerabilities: #{theme_vulns_count}"
puts "[#] Total fixed vulnerabilities: #{fix_theme_count}"
puts
puts "[#] Total plugins to enumerate: #{total_plugins}"
puts "[#] Total themes to enumerate: #{total_themes}"
puts
puts "[+] WordPress DB modified: #{date_wp.strftime('%Y-%m-%d %H:%M:%S')}"
puts "[+] Plugins DB modified: #{date_plugins.strftime('%Y-%m-%d %H:%M:%S')}"
puts "[+] Themes DB modified: #{date_themes.strftime('%Y-%m-%d %H:%M:%S')}"
puts "[+] Enumeration plugins: #{date_plugins_full.strftime('%Y-%m-%d %H:%M:%S')}"
puts "[+] Enumeration themes: #{date_themes_full.strftime('%Y-%m-%d %H:%M:%S')}"
puts
puts "[+] Report generated: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
end
end
def vuln_core_count(file=WP_VULNS_FILE)
json(file).size
end
def vuln_plugin_count(file=PLUGINS_VULNS_FILE)
json(file).size
end
def vuln_theme_count(file=THEMES_VULNS_FILE)
json(file).size
end
def version_vulns_count(file=WP_VULNS_FILE)
asset_vulns_count(json(file))
end
def fix_version_count(file=WP_VULNS_FILE)
asset_fixed_in_count(json(file))
end
def plugin_vulns_count(file=PLUGINS_VULNS_FILE)
asset_vulns_count(json(file))
end
def fix_plugin_count(file=PLUGINS_VULNS_FILE)
asset_fixed_in_count(json(file))
end
def theme_vulns_count(file=THEMES_VULNS_FILE)
asset_vulns_count(json(file))
end
def fix_theme_count(file=THEMES_VULNS_FILE)
asset_fixed_in_count(json(file))
end
def total_plugins(file=PLUGINS_FULL_FILE)
lines_in_file(file)
end
def total_themes(file=THEMES_FULL_FILE)
lines_in_file(file)
end
def lines_in_file(file)
IO.readlines(file).size
end
def asset_vulns_count(json)
json.map { |asset| asset[asset.keys.inject]['vulnerabilities'].size }.inject(:+)
end
def asset_fixed_in_count(json)
json.map { |asset| asset[asset.keys.inject]['vulnerabilities'].map {|a| a['fixed_in'].nil? ? 0 : 1 }.inject(:+) }.inject(:+)
end
end

View File

@@ -1,20 +0,0 @@
# encoding: UTF-8
require File.expand_path(File.dirname(__FILE__) + '/../common/common_helper')
require_files_from_directory(WPSTOOLS_LIB_DIR)
require_files_from_directory(WPSTOOLS_PLUGINS_DIR, '**/*.rb')
def usage
script_name = $0
puts
puts '-h for further help.'
puts
puts 'Examples:'
puts
puts 'Locally scan a wordpress installation for vulnerable files or shells'
puts "ruby #{script_name} --check-local-vulnerable-files /var/www/wordpress/"
puts
puts 'See README for further information.'
puts
end

View File

@@ -64,7 +64,7 @@ describe Browser do
it 'raises an error' do it 'raises an error' do
File.symlink('./testfile', config_file) File.symlink('./testfile', config_file)
expect { browser.load_config(config_file) }.to raise_error("[ERROR] Config file is a symlink.") expect { browser.load_config(config_file) }.to raise_error('[ERROR] Config file is a symlink.')
File.unlink(config_file) File.unlink(config_file)
end end
end end
@@ -130,7 +130,7 @@ describe Browser do
headers: { 'User-Agent' => 'SomeUA' }, headers: { 'User-Agent' => 'SomeUA' },
ssl_verifypeer: false, ssl_verifyhost: 0, ssl_verifypeer: false, ssl_verifyhost: 0,
cookiejar: cookie_jar, cookiefile: cookie_jar, cookiejar: cookie_jar, cookiefile: cookie_jar,
timeout: 2000, connecttimeout: 1000, timeout: 60, connecttimeout: 10,
maxredirs: 3, maxredirs: 3,
referer: nil referer: nil
} }

View File

@@ -92,7 +92,7 @@ describe CacheFileStore do
it 'should create a unique storage dir' do it 'should create a unique storage dir' do
storage_dirs = [] storage_dirs = []
(1..5).each do |i| (1..5).each do |_|
storage_dirs << CacheFileStore.new(cache_dir).storage_path storage_dirs << CacheFileStore.new(cache_dir).storage_path
end end

View File

@@ -17,10 +17,9 @@ describe 'WpTheme::Findable' do
wp_theme = WpTheme.send(:find_from_css_link, uri) wp_theme = WpTheme.send(:find_from_css_link, uri)
if @expected expect(wp_theme).to be_a WpTheme if @expected
expect(wp_theme).to be_a WpTheme
end
expect(wp_theme).to eq @expected expect(wp_theme).to eq @expected
expect(wp_theme.wp_content_dir).to eql 'wp-content' if @expected
end end
context 'when theme is not present' do context 'when theme is not present' do
@@ -59,6 +58,13 @@ describe 'WpTheme::Findable' do
end end
end end
# This one might introduce FP btw
context 'when leaked from comments' do
it 'returns the WpTheme' do
@file = 'comments.html'
@expected = WpTheme.new(uri, name: 'debug')
end
end
end end
describe '::find_from_wooframework' do describe '::find_from_wooframework' do
@@ -96,7 +102,6 @@ describe 'WpTheme::Findable' do
@expected = WpTheme.new(uri, name: 'Editorial', version: '1.3.5') @expected = WpTheme.new(uri, name: 'Editorial', version: '1.3.5')
end end
end end
end end
describe '::find' do describe '::find' do
@@ -109,7 +114,6 @@ describe 'WpTheme::Findable' do
context 'when a method is named s_find_from_s' do context 'when a method is named s_find_from_s' do
it 'does not call it' do it 'does not call it' do
class WpTheme class WpTheme
module Findable module Findable
extend self extend self
@@ -117,7 +121,7 @@ describe 'WpTheme::Findable' do
end end
end end
stub_all_to_nil() stub_all_to_nil
expect { WpTheme.find(uri) }.to_not raise_error expect { WpTheme.find(uri) }.to_not raise_error
end end
@@ -125,7 +129,7 @@ describe 'WpTheme::Findable' do
context 'when the theme is not found' do context 'when the theme is not found' do
it 'returns nil' do it 'returns nil' do
stub_all_to_nil() stub_all_to_nil
expect(WpTheme.find(uri)).to be_nil expect(WpTheme.find(uri)).to be_nil
end end
@@ -133,7 +137,7 @@ describe 'WpTheme::Findable' do
context 'when the theme is found' do context 'when the theme is found' do
it 'returns it, with the :found_from set' do it 'returns it, with the :found_from set' do
stub_all_to_nil() stub_all_to_nil
stub_request(:get, /.+\/the-oracle\/style.css$/).to_return(status: 200) stub_request(:get, /.+\/the-oracle\/style.css$/).to_return(status: 200)
expected = WpTheme.new(uri, name: 'the-oracle') expected = WpTheme.new(uri, name: 'the-oracle')

View File

@@ -178,7 +178,7 @@ describe 'WpVersion::Findable' do
context 'when no version found' do context 'when no version found' do
it 'returns nil' do it 'returns nil' do
stub_all_to_nil() stub_all_to_nil
@expected = nil @expected = nil
end end
end end
@@ -188,8 +188,8 @@ describe 'WpVersion::Findable' do
found_from = method[/^find_from_(.*)/, 1].sub('_', ' ') found_from = method[/^find_from_(.*)/, 1].sub('_', ' ')
context "when found from #{found_from}" do context "when found from #{found_from}" do
it "returns the correct WpVersion" do it 'returns the correct WpVersion' do
stub_all_to_nil() stub_all_to_nil
allow(WpVersion).to receive(method).and_return(number) allow(WpVersion).to receive(method).and_return(number)

View File

@@ -17,7 +17,7 @@ describe 'WebSite' do
) )
end end
describe "#new" do describe '#new' do
its(:url) { is_expected.to be === 'http://example.localhost/' } its(:url) { is_expected.to be === 'http://example.localhost/' }
end end
@@ -68,14 +68,14 @@ describe 'WebSite' do
describe '#xml_rpc_url' do describe '#xml_rpc_url' do
it 'returns the xmlrpc url' do it 'returns the xmlrpc url' do
expect(web_site.xml_rpc_url).to be === "http://example.localhost/xmlrpc.php" expect(web_site.xml_rpc_url).to be === 'http://example.localhost/xmlrpc.php'
end end
end end
describe '#has_xml_rpc?' do describe '#has_xml_rpc?' do
it 'returns true' do it 'returns true' do
stub_request(:get, web_site.xml_rpc_url). stub_request(:get, web_site.xml_rpc_url).
to_return(status: 200, body: "XML-RPC server accepts POST requests only") to_return(status: 200, body: 'XML-RPC server accepts POST requests only')
expect(web_site).to have_xml_rpc expect(web_site).to have_xml_rpc
end end
@@ -116,12 +116,24 @@ describe 'WebSite' do
expect(web_site.redirection).to eql absolute_location expect(web_site.redirection).to eql absolute_location
end end
context 'when starts with a ?' do
it 'returns the absolute URI' do
relative_location = '?p=blog'
absolute_location = web_site.uri.merge(relative_location).to_s
stub_request(:get, web_site.url).to_return(status: 301, headers: { location: relative_location })
stub_request(:get, absolute_location)
expect(web_site.redirection).to eql absolute_location
end
end
end end
context 'when multiple redirections' do context 'when multiple redirections' do
it 'returns the last redirection' do it 'returns the last redirection' do
first_redirection = 'www.redirection.com' first_redirection = 'http://www.redirection.com'
last_redirection = 'redirection.com' last_redirection = 'http://redirection.com'
stub_request(:get, web_site.url).to_return(status: 301, headers: { location: first_redirection }) stub_request(:get, web_site.url).to_return(status: 301, headers: { location: first_redirection })
stub_request(:get, first_redirection).to_return(status: 302, headers: { location: last_redirection }) stub_request(:get, first_redirection).to_return(status: 302, headers: { location: last_redirection })

View File

@@ -149,7 +149,7 @@ describe WpTarget do
after :each do after :each do
allow(wp_target).to receive_messages(wp_content_dir: 'wp-content') allow(wp_target).to receive_messages(wp_content_dir: 'wp-content')
stub_request_to_fixture(url: wp_target.debug_log_url(), fixture: @fixture) stub_request_to_fixture(url: wp_target.debug_log_url, fixture: @fixture)
expect(wp_target.has_debug_log?).to be === @expected expect(wp_target.has_debug_log?).to be === @expected
end end

View File

@@ -1,47 +0,0 @@
# encoding: UTF-8
require File.expand_path(File.dirname(__FILE__) + '/../../wpstools_helper')
describe 'StatsPlugin' do
subject(:stats) { StatsPlugin.new }
let(:plugins_vulns) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins_vulns.json' }
let(:themes_vulns) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' }
let(:plugins_file) { COLLECTIONS_FIXTURES + '/wp_plugins/detectable/targets.txt' }
let(:themes_file) { COLLECTIONS_FIXTURES + '/wp_themes/detectable/targets.txt'}
describe '#vuln_plugin_count' do
it 'returns the correct number' do
expect(stats.vuln_plugin_count(plugins_vulns)).to eq 2
end
end
describe '#vuln_theme_count' do
it 'returns the correct number' do
expect(stats.vuln_theme_count(themes_vulns)).to eq 2
end
end
describe '#plugin_vulns_count' do
it 'returns the correct number' do
expect(stats.plugin_vulns_count(plugins_vulns)).to eq 3
end
end
describe '#theme_vulns_count' do
it 'returns the correct number' do
expect(stats.theme_vulns_count(themes_vulns)).to eq 3
end
end
describe '#total_plugins' do
it 'returns the correct numer' do
expect(stats.total_plugins(plugins_file)).to eq 3
end
end
describe '#total_themes' do
it 'returns the correct numer' do
expect(stats.total_themes(themes_file)).to eq 3
end
end
end

View File

@@ -1,4 +0,0 @@
# encoding: UTF-8
require 'spec_helper'
require WPSTOOLS_LIB_DIR + '/wpstools_helper'

View File

@@ -0,0 +1,11 @@
<script type='text/javascript' src="http://wp.lab/wp-content/themes/debug/scripts/debug.js"></script>
<!-- W3 Total Cache: Minify debug info:
Engine: apc
Theme: 88e17
Template: page-home
Replaced CSS files:
1. wp-content/themes/debug/style.css
2. wp-content/themes/debug/css/responsive.css
-->

View File

@@ -4,7 +4,7 @@ Theme URI: http://wordpress.org/extend/themes/twentyeleven
Author: the WordPress team Author: the WordPress team
Author URI: http://wordpress.org/ Author URI: http://wordpress.org/
Description: The 2011 theme for WordPress is sophisticated, lightweight, and adaptable. Make it yours with a custom menu, header image, and background -- then go further with available theme options for light or dark color scheme, custom link colors, and three layout choices. Twenty Eleven comes equipped with a Showcase page template that transforms your front page into a showcase to show off your best content, widget support galore (sidebar, three footer areas, and a Showcase page widget area), and a custom "Ephemera" widget to display your Aside, Link, Quote, or Status posts. Included are styles for print and for the admin editor, support for featured images (as custom header images on posts and pages and as large images on featured "sticky" posts), and special styles for six different post formats. Description: The 2011 theme for WordPress is sophisticated, lightweight, and adaptable. Make it yours with a custom menu, header image, and background -- then go further with available theme options for light or dark color scheme, custom link colors, and three layout choices. Twenty Eleven comes equipped with a Showcase page template that transforms your front page into a showcase to show off your best content, widget support galore (sidebar, three footer areas, and a Showcase page widget area), and a custom "Ephemera" widget to display your Aside, Link, Quote, or Status posts. Included are styles for print and for the admin editor, support for featured images (as custom header images on posts and pages and as large images on featured "sticky" posts), and special styles for six different post formats.
Version: 1.3 Version: 1.3"
License: GNU General Public License License: GNU General Public License
License URI: license.txt License URI: license.txt
Tags: dark, light, white, black, gray, one-column, two-columns, left-sidebar, right-sidebar, fixed-width, flexible-width, custom-background, custom-colors, custom-header, custom-menu, editor-style, featured-image-header, featured-images, full-width-template, microformats, post-formats, rtl-language-support, sticky-post, theme-options, translation-ready Tags: dark, light, white, black, gray, one-column, two-columns, left-sidebar, right-sidebar, fixed-width, flexible-width, custom-background, custom-colors, custom-header, custom-menu, editor-style, featured-image-header, featured-images, full-width-template, microformats, post-formats, rtl-language-support, sticky-post, theme-options, translation-ready

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="renderer" content="webkit">
<title>一路疯下去</title>
<link rel="profile" href="http://gmpg.org/xfn/11" />
<link rel="pingback" href="http://wp.lab/xmlrpc.php" />
<link rel="canonical" href="http://wp.lab/author/一路疯下去/">
<body class="archive author author-78">

View File

@@ -1,7 +1,7 @@
{ {
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0",
"cache_ttl": 600, "cache_ttl": 600,
"request_timeout": 2000, "request_timeout": 60,
"connect_timeout": 1000, "connect_timeout": 10,
"max_threads": 20 "max_threads": 20
} }

View File

@@ -2,6 +2,6 @@
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:10.0) Gecko/20100101 Firefox/11.0", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:10.0) Gecko/20100101 Firefox/11.0",
"proxy": "127.0.0.1:3038", "proxy": "127.0.0.1:3038",
"cache_ttl": 300, "cache_ttl": 300,
"request_timeout": 2000, "request_timeout": 60,
"connect_timeout": 1000 "connect_timeout": 10
} }

View File

@@ -3,6 +3,6 @@
"proxy": "127.0.0.1:3038", "proxy": "127.0.0.1:3038",
"proxy_auth": "user:pass", "proxy_auth": "user:pass",
"cache_ttl": 300, "cache_ttl": 300,
"request_timeout": 2000, "request_timeout": 60,
"connect_timeout": 1000 "connect_timeout": 10
} }

View File

@@ -5,6 +5,7 @@ Disallow: /wordpress/admin/
Disallow: /wordpress/wp-admin/ Disallow: /wordpress/wp-admin/
Disallow: /wordpress/secret/ Disallow: /wordpress/secret/
Disallow: /Wordpress/wp-admin/ Disallow: /Wordpress/wp-admin/
Disallow: /wp-admin/tralling-space/
Allow: /asdf/ Allow: /asdf/
Sitemap: http://10.0.0.0/sitemap.xml.gz Sitemap: http://10.0.0.0/sitemap.xml.gz

View File

@@ -5,7 +5,7 @@ shared_examples 'WebSite::RobotsTxt' do
describe '#robots_url' do describe '#robots_url' do
it 'returns the correct url' do it 'returns the correct url' do
expect(web_site.robots_url).to be === 'http://example.localhost/robots.txt' expect(web_site.robots_url).to eql 'http://example.localhost/robots.txt'
end end
end end
@@ -57,6 +57,7 @@ shared_examples 'WebSite::RobotsTxt' do
http://example.localhost/wordpress/wp-admin/ http://example.localhost/wordpress/wp-admin/
http://example.localhost/wordpress/secret/ http://example.localhost/wordpress/secret/
http://example.localhost/Wordpress/wp-admin/ http://example.localhost/Wordpress/wp-admin/
http://example.localhost/wp-admin/tralling-space/
http://example.localhost/asdf/ http://example.localhost/asdf/
) )
end end
@@ -70,6 +71,7 @@ shared_examples 'WebSite::RobotsTxt' do
http://example.localhost/wordpress/admin/ http://example.localhost/wordpress/admin/
http://example.localhost/wordpress/secret/ http://example.localhost/wordpress/secret/
http://example.localhost/Wordpress/wp-admin/ http://example.localhost/Wordpress/wp-admin/
http://example.localhost/wp-admin/tralling-space/
http://example.localhost/asdf/ http://example.localhost/asdf/
) )
stub_request_to_fixture(url: web_site_sub.robots_url, fixture: fixture) stub_request_to_fixture(url: web_site_sub.robots_url, fixture: fixture)

View File

@@ -40,7 +40,7 @@ shared_examples 'WpTarget::WpRegistrable' do
end end
it 'returns true' do it 'returns true' do
@stub = { status: 200, body: %{<form id="setupform" method="post" action="wp-signup.php">} } @stub = { status: 200, body: '<form id="setupform" method="post" action="wp-signup.php">'}
@expected = true @expected = true
end end
end end
@@ -54,7 +54,7 @@ shared_examples 'WpTarget::WpRegistrable' do
end end
it 'returns true' do it 'returns true' do
@stub = { status: 200, body: %{<form name="registerform" id="registerform" action="wp-login.php"} } @stub = { status: 200, body: '<form name="registerform" id="registerform" action="wp-login.php"'}
@expected = true @expected = true
end end

View File

@@ -2,7 +2,7 @@
shared_examples 'WpUser::Existable' do shared_examples 'WpUser::Existable' do
let(:mod) { WpUser::Existable } let(:mod) { WpUser::Existable }
let(:fixtures_dir) { MODELS_FIXTURES + '/wp_user/existable' } let(:fixtures_dir) { File.join(MODELS_FIXTURES, 'wp_user', 'existable') }
describe '::login_from_author_pattern' do describe '::login_from_author_pattern' do
after do after do
@@ -145,7 +145,7 @@ shared_examples 'WpUser::Existable' do
end end
context 'with a 200' do context 'with a 200' do
let(:resp_opt) { { code: 200, body: File.new(fixtures_dir + '/admin.html').read } } let(:resp_opt) { { code: 200, body: File.read(File.join(fixtures_dir, 'admin.html')) } }
it 'loads the correct values' do it 'loads the correct values' do
@login = 'admin' @login = 'admin'
@@ -153,6 +153,15 @@ shared_examples 'WpUser::Existable' do
end end
end end
context 'when chinese chars' do
let(:resp_opt) { { code: 200, body: File.read(File.join(fixtures_dir, 'chinese_chars.html')) } }
it 'loads the correct values' do
@login = '一路疯下去'
@display_name = nil
end
end
context 'otherwise' do context 'otherwise' do
it 'does not do anything' do it 'does not do anything' do
@resp_opt = { code: 404 } @resp_opt = { code: 404 }

View File

@@ -5,6 +5,8 @@ require 'webmock/rspec'
# Code Coverage (only works with ruby >= 1.9) # Code Coverage (only works with ruby >= 1.9)
require 'simplecov' if RUBY_VERSION >= '1.9' require 'simplecov' if RUBY_VERSION >= '1.9'
RSpec::Expectations.configuration.warn_about_potential_false_positives = false
require File.expand_path(File.dirname(__FILE__) + '/../lib/common/common_helper') require File.expand_path(File.dirname(__FILE__) + '/../lib/common/common_helper')
SPEC_DIR = ROOT_DIR + '/spec' SPEC_DIR = ROOT_DIR + '/spec'

View File

@@ -10,11 +10,11 @@ describe 'wpscan main checks' do
end end
it 'should check for valid syntax' do it 'should check for valid syntax' do
result = "" result = ''
Dir.glob("**/*.rb") do |file| Dir.glob('**/*.rb') do |file|
res = %x{#{RbConfig.ruby} -c #{ROOT_DIR}/#{file} 2>&1}.split("\n") res = %x{#{RbConfig.ruby} -c #{ROOT_DIR}/#{file} 2>&1}.split("\n")
ok = res.select {|msg| msg =~ /Syntax OK/} ok = res.select {|msg| msg =~ /Syntax OK/}
result << ("####################\nSyntax error in #{file}:\n#{res.join("\n").strip()}\n") if ok.size != 1 result << ("####################\nSyntax error in #{file}:\n#{res.join("\n").strip}\n") if ok.size != 1
end end
fail(result) unless result.empty? fail(result) unless result.empty?
end end

140
wpscan.rb
View File

@@ -2,6 +2,9 @@
# encoding: UTF-8 # encoding: UTF-8
$: << '.' $: << '.'
$exit_code = 0
require File.dirname(__FILE__) + '/lib/wpscan/wpscan_helper' require File.dirname(__FILE__) + '/lib/wpscan/wpscan_helper'
def main def main
@@ -13,7 +16,7 @@ def main
$log = wpscan_options.log $log = wpscan_options.log
banner() # called after $log set banner() unless wpscan_options.no_banner # called after $log set
unless wpscan_options.has_options? unless wpscan_options.has_options?
# first parameter only url? # first parameter only url?
@@ -36,6 +39,8 @@ def main
if wpscan_options.version if wpscan_options.version
puts "Current version: #{WPSCAN_VERSION}" puts "Current version: #{WPSCAN_VERSION}"
date = last_update
puts "Last DB update: #{date.strftime('%Y-%m-%d')}" unless date.nil?
exit(0) exit(0)
end end
@@ -45,10 +50,28 @@ def main
wpscan_options.to_h.merge(max_threads: wpscan_options.threads) wpscan_options.to_h.merge(max_threads: wpscan_options.threads)
) )
if wpscan_options.update || missing_db_file? # check if db file needs upgrade and we are not running in batch mode
puts "#{notice('[i]')} Updating the Database ..." # also no need to check if the user supplied the --update switch
if update_required? && !wpscan_options.batch && !wpscan_options.update
puts notice('It seems like you have not updated the database for some time.')
print '[?] Do you want to update now? [Y]es [N]o [A]bort, default: [N]'
if (input = Readline.readline) =~ /^y/i
wpscan_options.update = true
elsif input =~ /^a/i
puts 'Scan aborted'
exit(1)
else
if missing_db_file?
puts critical('You can not run a scan without any databases.')
exit(1)
end
end
end
if wpscan_options.update
puts notice('Updating the Database ...')
DbUpdater.new(DATA_DIR).update(wpscan_options.verbose) DbUpdater.new(DATA_DIR).update(wpscan_options.verbose)
puts "#{notice('[i]')} Update completed." puts notice('Update completed.')
# Exit program if only option --update is used # Exit program if only option --update is used
exit(0) unless wpscan_options.url exit(0) unless wpscan_options.url
end end
@@ -74,10 +97,13 @@ def main
# Remote website has a redirection? # Remote website has a redirection?
if (redirection = wp_target.redirection) if (redirection = wp_target.redirection)
if redirection =~ /\/wp-admin\/install\.php$/
puts critical('The Website is not fully configured and currently in install mode. Call it to create a new admin user.')
else
if wpscan_options.follow_redirection if wpscan_options.follow_redirection
puts "Following redirection #{redirection}" puts "Following redirection #{redirection}"
else else
puts "#{notice('[i]')} The remote host tried to redirect to: #{redirection}" puts notice("The remote host tried to redirect to: #{redirection}")
print '[?] Do you want follow the redirection ? [Y]es [N]o [A]bort, default: [N]' print '[?] Do you want follow the redirection ? [Y]es [N]o [A]bort, default: [N]'
end end
if wpscan_options.follow_redirection || !wpscan_options.batch if wpscan_options.follow_redirection || !wpscan_options.batch
@@ -87,7 +113,8 @@ def main
else else
if input =~ /^a/i if input =~ /^a/i
puts 'Scan aborted' puts 'Scan aborted'
exit(0) exit(1)
end
end end
end end
end end
@@ -106,7 +133,7 @@ def main
# Remote website is wordpress? # Remote website is wordpress?
unless wpscan_options.force unless wpscan_options.force
unless wp_target.wordpress? unless wp_target.wordpress?
raise "#{critical('[!]')} The remote website is up, but does not seem to be running WordPress." raise critical('The remote website is up, but does not seem to be running WordPress.')
end end
end end
@@ -119,51 +146,51 @@ def main
puts 'You can specify one per command line option (don\'t forget to include the wp-content directory if needed)' puts 'You can specify one per command line option (don\'t forget to include the wp-content directory if needed)'
puts '[?] Continue? [Y]es [N]o, default: [N]' puts '[?] Continue? [Y]es [N]o, default: [N]'
if wpscan_options.batch || Readline.readline !~ /^y/i if wpscan_options.batch || Readline.readline !~ /^y/i
exit(0) exit(1)
end end
end end
# Output runtime data # Output runtime data
start_time = Time.now start_time = Time.now
start_memory = get_memory_usage start_memory = get_memory_usage
puts "#{info('[+]')} URL: #{wp_target.url}" puts info("URL: #{wp_target.url}")
puts "#{info('[+]')} Started: #{start_time.asctime}" puts info("Started: #{start_time.asctime}")
puts puts
if wp_target.wordpress_hosted? if wp_target.wordpress_hosted?
puts "#{critical('[!]')} We do not support scanning *.wordpress.com hosted blogs" puts critical('We do not support scanning *.wordpress.com hosted blogs')
end end
if wp_target.has_robots? if wp_target.has_robots?
puts "#{info('[+]')} robots.txt available under: '#{wp_target.robots_url}'" puts info("robots.txt available under: '#{wp_target.robots_url}'")
wp_target.parse_robots_txt.each do |dir| wp_target.parse_robots_txt.each do |dir|
puts "#{info('[+]')} Interesting entry from robots.txt: #{dir}" puts info("Interesting entry from robots.txt: #{dir}")
end end
end end
if wp_target.has_readme? if wp_target.has_readme?
puts "#{warning('[!]')} The WordPress '#{wp_target.readme_url}' file exists exposing a version number" puts warning("The WordPress '#{wp_target.readme_url}' file exists exposing a version number")
end end
if wp_target.has_full_path_disclosure? if wp_target.has_full_path_disclosure?
puts "#{warning('[!]')} Full Path Disclosure (FPD) in: '#{wp_target.full_path_disclosure_url}'" puts warning("Full Path Disclosure (FPD) in '#{wp_target.full_path_disclosure_url}': #{wp_target.full_path_disclosure_data}")
end end
if wp_target.has_debug_log? if wp_target.has_debug_log?
puts "#{critical('[!]')} Debug log file found: #{wp_target.debug_log_url}" puts critical("Debug log file found: #{wp_target.debug_log_url}")
end end
wp_target.config_backup.each do |file_url| wp_target.config_backup.each do |file_url|
puts "#{critical('[!]')} A wp-config.php backup file has been found in: '#{file_url}'" puts critical("A wp-config.php backup file has been found in: '#{file_url}'")
end end
if wp_target.search_replace_db_2_exists? if wp_target.search_replace_db_2_exists?
puts "#{critical('[!]')} searchreplacedb2.php has been found in: '#{wp_target.search_replace_db_2_url}'" puts critical("searchreplacedb2.php has been found in: '#{wp_target.search_replace_db_2_url}'")
end end
wp_target.interesting_headers.each do |header| wp_target.interesting_headers.each do |header|
output = "#{info('[+]')} Interesting header: " output = info('Interesting header: ')
if header[1].class == Array if header[1].class == Array
header[1].each do |value| header[1].each do |value|
@@ -175,24 +202,23 @@ def main
end end
if wp_target.multisite? if wp_target.multisite?
puts "#{info('[+]')} This site seems to be a multisite (http://codex.wordpress.org/Glossary#Multisite)" puts info('This site seems to be a multisite (http://codex.wordpress.org/Glossary#Multisite)')
end end
if wp_target.has_must_use_plugins? if wp_target.has_must_use_plugins?
puts "#{info('[+]')} This site has 'Must Use Plugins' (http://codex.wordpress.org/Must_Use_Plugins)" puts info("This site has 'Must Use Plugins' (http://codex.wordpress.org/Must_Use_Plugins)")
end end
if wp_target.registration_enabled? if wp_target.registration_enabled?
puts "#{warning('[+]')} Registration is enabled: #{wp_target.registration_url}" puts warning("Registration is enabled: #{wp_target.registration_url}")
end end
if wp_target.has_xml_rpc? if wp_target.has_xml_rpc?
puts "#{info('[+]')} XML-RPC Interface available under: #{wp_target.xml_rpc_url}" puts info("XML-RPC Interface available under: #{wp_target.xml_rpc_url}")
puts "#{notice('[i]')} This may allow the GHOST vulnerability to be exploited, please see: https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_ghost_scanner"
end end
if wp_target.upload_directory_listing_enabled? if wp_target.upload_directory_listing_enabled?
puts "#{warning('[!]')} Upload directory has directory listing enabled: #{wp_target.upload_dir_url}" puts warning("Upload directory has directory listing enabled: #{wp_target.upload_dir_url}")
end end
enum_options = { enum_options = {
@@ -204,13 +230,13 @@ def main
wp_version.output(wpscan_options.verbose) wp_version.output(wpscan_options.verbose)
else else
puts puts
puts "#{notice('[i]')} WordPress version can not be detected" puts notice('WordPress version can not be detected')
end end
if wp_theme = wp_target.theme if wp_theme = wp_target.theme
puts puts
# Theme version is handled in #to_s # Theme version is handled in #to_s
puts "#{info('[+]')} WordPress theme in use: #{wp_theme}" puts info("WordPress theme in use: #{wp_theme}")
wp_theme.output(wpscan_options.verbose) wp_theme.output(wpscan_options.verbose)
# Check for parent Themes # Check for parent Themes
@@ -220,7 +246,7 @@ def main
parent = wp_theme.get_parent_theme parent = wp_theme.get_parent_theme
puts puts
puts "#{info('[+]')} Detected parent theme: #{parent}" puts info("Detected parent theme: #{parent}")
parent.output(wpscan_options.verbose) parent.output(wpscan_options.verbose)
wp_theme = parent wp_theme = parent
end end
@@ -229,22 +255,25 @@ def main
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
puts "#{info('[+]')} Enumerating plugins from passive detection ..." puts info('Enumerating plugins from passive detection ...')
wp_plugins = WpPlugins.passive_detection(wp_target) wp_plugins = WpPlugins.passive_detection(wp_target)
if !wp_plugins.empty? if !wp_plugins.empty?
if wp_plugins.size == 1
puts " | #{wp_plugins.size} plugin found:"
else
puts " | #{wp_plugins.size} plugins found:" puts " | #{wp_plugins.size} plugins found:"
end
wp_plugins.output(wpscan_options.verbose) wp_plugins.output(wpscan_options.verbose)
else else
puts "#{info('[+]')} No plugins found" puts info('No plugins found')
end end
end end
# Enumerate the installed plugins # Enumerate the installed plugins
if wpscan_options.enumerate_plugins or wpscan_options.enumerate_only_vulnerable_plugins or wpscan_options.enumerate_all_plugins if wpscan_options.enumerate_plugins or wpscan_options.enumerate_only_vulnerable_plugins or wpscan_options.enumerate_all_plugins
puts puts
puts "#{info('[+]')} Enumerating installed plugins #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_plugins} ..." puts info("Enumerating installed plugins #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_plugins} ...")
puts puts
wp_plugins = WpPlugins.aggressive_detection(wp_target, wp_plugins = WpPlugins.aggressive_detection(wp_target,
@@ -255,18 +284,18 @@ def main
) )
puts puts
if !wp_plugins.empty? if !wp_plugins.empty?
puts "#{info('[+]')} We found #{wp_plugins.size} plugins:" puts info("We found #{wp_plugins.size} plugins:")
wp_plugins.output(wpscan_options.verbose) wp_plugins.output(wpscan_options.verbose)
else else
puts "#{info('[+]')} No plugins found" puts info('No plugins found')
end end
end end
# Enumerate installed themes # Enumerate installed themes
if wpscan_options.enumerate_themes or wpscan_options.enumerate_only_vulnerable_themes or wpscan_options.enumerate_all_themes if wpscan_options.enumerate_themes or wpscan_options.enumerate_only_vulnerable_themes or wpscan_options.enumerate_all_themes
puts puts
puts "#{info('[+]')} Enumerating installed themes #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_themes} ..." puts info("Enumerating installed themes #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_themes} ...")
puts puts
wp_themes = WpThemes.aggressive_detection(wp_target, wp_themes = WpThemes.aggressive_detection(wp_target,
@@ -277,17 +306,17 @@ def main
) )
puts puts
if !wp_themes.empty? if !wp_themes.empty?
puts "#{info('[+]')} We found #{wp_themes.size} themes:" puts info("We found #{wp_themes.size} themes:")
wp_themes.output(wpscan_options.verbose) wp_themes.output(wpscan_options.verbose)
else else
puts "#{info('[+]')} No themes found" puts info('No themes found')
end end
end end
if wpscan_options.enumerate_timthumbs if wpscan_options.enumerate_timthumbs
puts puts
puts "#{info('[+]')} Enumerating timthumb files ..." puts info('Enumerating timthumb files ...')
puts puts
wp_timthumbs = WpTimthumbs.aggressive_detection(wp_target, wp_timthumbs = WpTimthumbs.aggressive_detection(wp_target,
@@ -298,22 +327,21 @@ def main
) )
puts puts
if !wp_timthumbs.empty? if !wp_timthumbs.empty?
puts "#{info('[+]')} We found #{wp_timthumbs.size} timthumb file/s:" puts info("We found #{wp_timthumbs.size} timthumb file/s:")
wp_timthumbs.output(wpscan_options.verbose) wp_timthumbs.output(wpscan_options.verbose)
else else
puts "#{info('[+]')} No timthumb files found" puts info('No timthumb files found')
end end
end end
# If we haven't been supplied a username/usernames list, enumerate them... # If we haven't been supplied a username/usernames list, enumerate them...
if !wpscan_options.username && !wpscan_options.usernames && wpscan_options.wordlist || wpscan_options.enumerate_usernames if !wpscan_options.username && !wpscan_options.usernames && wpscan_options.wordlist || wpscan_options.enumerate_usernames
puts puts
puts "#{info('[+]')} Enumerating usernames ..." puts info('Enumerating usernames ...')
if wp_target.has_plugin?('stop-user-enumeration') if wp_target.has_plugin?('stop-user-enumeration')
puts "#{warning('[!]')} Stop User Enumeration plugin detected, results might be empty. " \ puts warning("Stop User Enumeration plugin detected, results might be empty. However a bypass exists for v1.2.8 and below, see stop_user_enumeration_bypass.rb in #{File.expand_path(File.dirname(__FILE__))}")
"However a bypass exists for v1.2.8 and below, see stop_user_enumeration_bypass.rb in #{File.expand_path(File.dirname(__FILE__))}"
end end
wp_users = WpUsers.aggressive_detection(wp_target, wp_users = WpUsers.aggressive_detection(wp_target,
@@ -324,7 +352,7 @@ def main
) )
if wp_users.empty? if wp_users.empty?
puts "#{info('[+]')} We did not enumerate any usernames" puts info('We did not enumerate any usernames')
if wpscan_options.wordlist if wpscan_options.wordlist
puts 'Try supplying your own username with the --username option' puts 'Try supplying your own username with the --username option'
@@ -332,10 +360,10 @@ def main
exit(1) exit(1)
end end
else else
puts "#{info('[+]')} Identified the following #{wp_users.size} user/s:" puts info("Identified the following #{wp_users.size} user/s:")
wp_users.output(margin_left: ' ' * 4) wp_users.output(margin_left: ' ' * 4)
if wp_users[0].login == "admin" if wp_users[0].login == "admin"
puts "#{warning('[!]')} Default first WordPress username 'admin' is still used" puts warning("Default first WordPress username 'admin' is still used")
end end
end end
@@ -359,14 +387,14 @@ def main
protection_plugin = wp_target.login_protection_plugin() protection_plugin = wp_target.login_protection_plugin()
puts puts
puts "#{warning('[!]')} The plugin #{protection_plugin.name} has been detected. It might record the IP and timestamp of every failed login and/or prevent brute forcing altogether. Not a good idea for brute forcing!" puts warning("The plugin #{protection_plugin.name} has been detected. It might record the IP and timestamp of every failed login and/or prevent brute forcing altogether. Not a good idea for brute forcing!")
puts '[?] Do you want to start the brute force anyway ? [Y]es [N]o, default: [N]' puts '[?] Do you want to start the brute force anyway ? [Y]es [N]o, default: [N]'
bruteforce = false if wpscan_options.batch || Readline.readline !~ /^y/i bruteforce = false if wpscan_options.batch || Readline.readline !~ /^y/i
end end
if bruteforce if bruteforce
puts "#{info('[+]')} Starting the password brute forcer" puts info('Starting the password brute forcer')
begin begin
wp_users.brute_force( wp_users.brute_force(
@@ -379,7 +407,7 @@ def main
wp_users.output(show_password: true, margin_left: ' ' * 2) wp_users.output(show_password: true, margin_left: ' ' * 2)
end end
else else
puts "#{critical('[!]')} Brute forcing aborted" puts critical('Brute forcing aborted')
end end
end end
@@ -388,14 +416,13 @@ def main
used_memory = get_memory_usage - start_memory used_memory = get_memory_usage - start_memory
puts puts
puts info("[+] Finished: #{stop_time.asctime}") puts info("Finished: #{stop_time.asctime}")
puts info("[+] Requests Done: #{@total_requests_done}") puts info("Requests Done: #{@total_requests_done}")
puts info("[+] Memory used: #{used_memory.bytes_to_human}") puts info("Memory used: #{used_memory.bytes_to_human}")
puts info("[+] Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}") puts info("Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}")
exit(0) # must exit!
rescue SystemExit, Interrupt
rescue Interrupt
# do nothing on interrupt
rescue => e rescue => e
puts puts
puts critical(e.message) puts critical(e.message)
@@ -414,3 +441,4 @@ def main
end end
main() main()
exit($exit_code)

View File

@@ -1,44 +0,0 @@
#!/usr/bin/env ruby
# encoding: UTF-8
$: << '.'
require File.dirname(__FILE__) + '/lib/wpstools/wpstools_helper'
begin
# delete old logfile, check if it is a symlink first.
File.delete(LOG_FILE) if File.exist?(LOG_FILE) and !File.symlink?(LOG_FILE)
banner()
option_parser = CustomOptionParser.new('Usage: ./wpstools.rb [options]', 60)
option_parser.separator ''
option_parser.add(['-v', '--verbose', 'Verbose output'])
plugins = Plugins.new(option_parser)
plugins.register(
CheckerPlugin.new,
StatsPlugin.new,
CheckerSpelling.new
)
options = option_parser.results
if options.empty?
raise "No option supplied\n\n#{option_parser}"
end
plugins.each do |plugin|
plugin.run(options)
end
exit(0)
rescue => e
puts "[ERROR] #{e.message}"
unless e.backtrace[0] =~ /main/
puts 'Trace :'
puts e.backtrace.join("\n")
end
exit(1)
end