Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f832e27b49 | ||
|
|
6ce29f73c5 | ||
|
|
920338fb62 | ||
|
|
49d0a9e6d9 | ||
|
|
fe401e622b | ||
|
|
6e32cb0db2 | ||
|
|
73171eb39d | ||
|
|
2e05f4171e | ||
|
|
75b8c303e2 | ||
|
|
bd7a493f1c | ||
|
|
9dada7c8f4 | ||
|
|
fe7aede458 | ||
|
|
cdf2b38780 | ||
|
|
a09dbab6a8 | ||
|
|
49a6d275d2 | ||
|
|
8192a4a215 | ||
|
|
1d6593fd4d | ||
|
|
bf99e31e70 | ||
|
|
5386496bdc | ||
|
|
6451510449 | ||
|
|
cd68aa719c | ||
|
|
b328dc4ff9 | ||
|
|
1e1c79aa56 | ||
|
|
08650ce156 | ||
|
|
a1929719f3 | ||
|
|
d34da72cd3 | ||
|
|
816b18b604 | ||
|
|
a78a13bf3f | ||
|
|
33f8aaf1dc | ||
|
|
26ab95d822 | ||
|
|
cea01d8aa0 | ||
|
|
0e61f1e284 | ||
|
|
ddef061b90 | ||
|
|
addeab8947 | ||
|
|
55dc665404 | ||
|
|
8f8538e9e9 | ||
|
|
348ca55bee | ||
|
|
1bb5bc7f33 | ||
|
|
3be5e1fcf5 | ||
|
|
9df8cc9243 | ||
|
|
e28c84aa34 | ||
|
|
7db6b54761 | ||
|
|
e3a06f5694 | ||
|
|
7c5d15e098 | ||
|
|
d683c0f151 | ||
|
|
1e67fa26ff | ||
|
|
0ae6ef59ec | ||
|
|
e27ef40e0f | ||
|
|
380760d028 | ||
|
|
18cfdafc19 | ||
|
|
0934a2e329 | ||
|
|
d1a320324e |
@@ -1 +1 @@
|
|||||||
2.2.3
|
2.3.1
|
||||||
|
|||||||
17
.travis.yml
17
.travis.yml
@@ -2,28 +2,21 @@ language: ruby
|
|||||||
sudo: false
|
sudo: false
|
||||||
cache: bundler
|
cache: bundler
|
||||||
rvm:
|
rvm:
|
||||||
- 1.9.2
|
# Still not in Travis :(
|
||||||
- 1.9.3
|
# - 2.1.9
|
||||||
- 2.0.0
|
|
||||||
- 2.1.0
|
|
||||||
- 2.1.1
|
|
||||||
- 2.1.2
|
|
||||||
- 2.1.3
|
|
||||||
- 2.1.4
|
|
||||||
- 2.1.5
|
|
||||||
- 2.2.0
|
- 2.2.0
|
||||||
- 2.2.1
|
- 2.2.1
|
||||||
- 2.2.2
|
- 2.2.2
|
||||||
- 2.2.3
|
- 2.2.3
|
||||||
|
- 2.2.4
|
||||||
|
- 2.3.0
|
||||||
|
- 2.3.1
|
||||||
before_install:
|
before_install:
|
||||||
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||||
script: bundle exec rspec
|
script: bundle exec rspec
|
||||||
notifications:
|
notifications:
|
||||||
email:
|
email:
|
||||||
- team@wpscan.org
|
- team@wpscan.org
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- rvm: 1.9.2
|
|
||||||
# do not build gh-pages branch
|
# do not build gh-pages branch
|
||||||
branches:
|
branches:
|
||||||
except:
|
except:
|
||||||
|
|||||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,6 +1,23 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
## Master
|
## Master
|
||||||
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.9...master)
|
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.9.1...master)
|
||||||
|
|
||||||
|
## Version 2.9.1
|
||||||
|
Released: 2016-05-06
|
||||||
|
|
||||||
|
* Update to Ruby 2.3.1, drop older ruby support
|
||||||
|
* New data file location
|
||||||
|
* Added experimental Windows support
|
||||||
|
* Display WordPress metadata on the detected version
|
||||||
|
* Several small fixes
|
||||||
|
|
||||||
|
WPScan Database Statistics:
|
||||||
|
* Total vulnerable versions: 156
|
||||||
|
* Total vulnerable plugins: 1324
|
||||||
|
* Total vulnerable themes: 376
|
||||||
|
* Total version vulnerabilities: 1998
|
||||||
|
* Total plugin vulnerabilities: 2057
|
||||||
|
* Total theme vulnerabilities: 449
|
||||||
|
|
||||||
## Version 2.9
|
## Version 2.9
|
||||||
Released: 2015-10-15
|
Released: 2015-10-15
|
||||||
@@ -137,7 +154,7 @@ New
|
|||||||
* Add Sucuri sponsor to banner
|
* Add Sucuri sponsor to banner
|
||||||
* Add protocol to sucuri url in banner
|
* Add protocol to sucuri url in banner
|
||||||
* Add response code to proxy error output
|
* Add response code to proxy error output
|
||||||
* Add a statement about mendatory newlines at the end of list
|
* Add a statement about mandatory newlines at the end of list
|
||||||
* Give warning if default username 'admin' is still used
|
* Give warning if default username 'admin' is still used
|
||||||
* License amendment to make it more clear about value added usage
|
* License amendment to make it more clear about value added usage
|
||||||
|
|
||||||
@@ -493,4 +510,3 @@ Fixed issues
|
|||||||
|
|
||||||
## Version 2.1
|
## Version 2.1
|
||||||
Released 2013-3-4
|
Released 2013-3-4
|
||||||
|
|
||||||
|
|||||||
5
Gemfile
5
Gemfile
@@ -1,10 +1,11 @@
|
|||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
gem 'typhoeus', '~>0.8.0'
|
gem 'typhoeus', '>=0.8.0'
|
||||||
gem 'nokogiri'
|
gem 'nokogiri', '>=1.6.7.1'
|
||||||
gem 'addressable'
|
gem 'addressable'
|
||||||
gem 'yajl-ruby' # Better JSON parser regarding memory usage
|
gem 'yajl-ruby' # Better JSON parser regarding memory usage
|
||||||
# TODO: update the below when terminal-table 1.5.3+ is released.
|
# TODO: update the below when terminal-table 1.5.3+ is released.
|
||||||
|
# See issue #841 for details
|
||||||
# (and delete the Terminal module in lib/common/hacks.rb)
|
# (and delete the Terminal module in lib/common/hacks.rb)
|
||||||
gem 'terminal-table', '~>1.4.5'
|
gem 'terminal-table', '~>1.4.5'
|
||||||
gem 'ruby-progressbar', '>=1.6.0'
|
gem 'ruby-progressbar', '>=1.6.0'
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
WPScan Public Source License
|
WPScan Public Source License
|
||||||
|
|
||||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2015 WPScan Team.
|
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2016 WPScan Team.
|
||||||
|
|
||||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
||||||
|
|
||||||
|
|||||||
95
README.md
95
README.md
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
#### WPScan Public Source License
|
#### WPScan Public Source License
|
||||||
|
|
||||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2015 WPScan Team.
|
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2016 WPScan Team.
|
||||||
|
|
||||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ WPScan comes pre-installed on the following Linux distributions:
|
|||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
- Ruby >= 1.9.2 - Recommended: 2.2.3
|
- Ruby >= 2.1.9 - Recommended: 2.3.1
|
||||||
- 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
|
||||||
@@ -118,7 +118,7 @@ From Ubuntu 14.04:
|
|||||||
|
|
||||||
####Installing on Debian:
|
####Installing on Debian:
|
||||||
|
|
||||||
sudo apt-get install git ruby ruby-dev libcurl4-openssl-dev make
|
sudo apt-get install git ruby ruby-dev libcurl4-openssl-dev make zlib1g-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
|
sudo gem install bundler
|
||||||
@@ -126,7 +126,7 @@ From Ubuntu 14.04:
|
|||||||
|
|
||||||
####Installing on Fedora:
|
####Installing on Fedora:
|
||||||
|
|
||||||
sudo yum install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel patch
|
sudo dnf install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel patch rpm-build
|
||||||
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
|
||||||
@@ -149,14 +149,15 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
|||||||
cd wpscan
|
cd wpscan
|
||||||
sudo gem install bundler && sudo bundle install --without test
|
sudo gem install bundler && sudo bundle install --without test
|
||||||
|
|
||||||
####Installing with RVM:
|
####Installing with RVM (recommended):
|
||||||
|
|
||||||
|
# Install all prerequisites for your OS (look above)
|
||||||
cd ~
|
cd ~
|
||||||
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.3
|
rvm install 2.3.1
|
||||||
rvm use 2.2.3 --default
|
rvm use 2.3.1 --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
|
||||||
@@ -191,7 +192,7 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
|||||||
|
|
||||||
Then, open the directory of the readline gem (you have to locate it)
|
Then, open the directory of the readline gem (you have to locate it)
|
||||||
|
|
||||||
cd ~/.rvm/src/ruby-1.9.2-p180/ext/readline
|
cd ~/.rvm/src/ruby-XXXX/ext/readline
|
||||||
ruby extconf.rb
|
ruby extconf.rb
|
||||||
make
|
make
|
||||||
make install
|
make install
|
||||||
@@ -209,12 +210,9 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
|||||||
|
|
||||||
#### WPSCAN ARGUMENTS
|
#### WPSCAN ARGUMENTS
|
||||||
|
|
||||||
--update Update the databases.
|
--update Update the database to the latest version.
|
||||||
|
|
||||||
--url | -u <target url> The WordPress URL/domain to scan.
|
--url | -u <target url> The WordPress URL/domain to scan.
|
||||||
|
|
||||||
--force | -f Forces WPScan to not check if the remote site is running WordPress.
|
--force | -f Forces WPScan to not check if the remote site is running WordPress.
|
||||||
|
|
||||||
--enumerate | -e [option(s)] Enumeration.
|
--enumerate | -e [option(s)] Enumeration.
|
||||||
option :
|
option :
|
||||||
u usernames from id 1 to 10
|
u usernames from id 1 to 10
|
||||||
@@ -229,53 +227,36 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
|||||||
Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
|
Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
|
||||||
If no option is supplied, the default is "vt,tt,u,vp"
|
If no option is supplied, the default is "vt,tt,u,vp"
|
||||||
|
|
||||||
--exclude-content-based "<regexp or string>" Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied
|
--exclude-content-based "<regexp or string>"
|
||||||
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double)
|
Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied.
|
||||||
|
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).
|
||||||
--config-file | -c <config file> Use the specified config file, see the example.conf.json
|
--config-file | -c <config file> Use the specified config file, see the example.conf.json.
|
||||||
|
--user-agent | -a <User-Agent> Use the specified User-Agent.
|
||||||
--user-agent | -a <User-Agent> Use the specified User-Agent
|
--cookie <String> String to read cookies from.
|
||||||
|
--random-agent | -r Use a random User-Agent.
|
||||||
--random-agent | -r Use a random User-Agent
|
|
||||||
|
|
||||||
--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not
|
--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not
|
||||||
|
--batch Never ask for user input, use the default behaviour.
|
||||||
--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it. Subdirectories are allowed
|
|
||||||
|
|
||||||
--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory. If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed
|
|
||||||
|
|
||||||
--proxy <[protocol://]host:port> Supply a proxy (will override the one from conf/browser.conf.json).
|
|
||||||
HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported. If no protocol is given (format host:port), HTTP will be used
|
|
||||||
|
|
||||||
--proxy-auth <username:password> Supply the proxy login credentials.
|
|
||||||
|
|
||||||
--basic-auth <username:password> Set the HTTP Basic authentication.
|
|
||||||
|
|
||||||
--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.
|
|
||||||
|
|
||||||
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
|
|
||||||
|
|
||||||
--username | -U <username> Only brute force the supplied username.
|
|
||||||
|
|
||||||
--usernames <path-to-file> Only brute force the usernames from the file.
|
|
||||||
|
|
||||||
--cache-ttl <cache-ttl> Typhoeus cache TTL.
|
|
||||||
|
|
||||||
--request-timeout <request-timeout> Request Timeout.
|
|
||||||
|
|
||||||
--connect-timeout <connect-timeout> Connect Timeout.
|
|
||||||
|
|
||||||
--max-threads <max-threads> Maximum Threads.
|
|
||||||
|
|
||||||
--help | -h This help screen.
|
|
||||||
|
|
||||||
--verbose | -v Verbose output.
|
|
||||||
|
|
||||||
--batch Never ask for user input, use the default behavior.
|
|
||||||
|
|
||||||
--no-color Do not use colors in the output.
|
--no-color Do not use colors in the output.
|
||||||
|
--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it.
|
||||||
--log Save STDOUT to log.txt
|
Subdirectories are allowed.
|
||||||
|
--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory.
|
||||||
|
If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed
|
||||||
|
--proxy <[protocol://]host:port> Supply a proxy. HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported.
|
||||||
|
If no protocol is given (format host:port), HTTP will be used.
|
||||||
|
--proxy-auth <username:password> Supply the proxy login credentials.
|
||||||
|
--basic-auth <username:password> Set the HTTP Basic authentication.
|
||||||
|
--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.
|
||||||
|
--username | -U <username> Only brute force the supplied username.
|
||||||
|
--usernames <path-to-file> Only brute force the usernames from the file.
|
||||||
|
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
|
||||||
|
--cache-ttl <cache-ttl> Typhoeus cache TTL.
|
||||||
|
--request-timeout <request-timeout> Request Timeout.
|
||||||
|
--connect-timeout <connect-timeout> Connect Timeout.
|
||||||
|
--max-threads <max-threads> Maximum Threads.
|
||||||
|
--throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.
|
||||||
|
--help | -h This help screen.
|
||||||
|
--verbose | -v Verbose output.
|
||||||
|
--version Output the current version and exit.
|
||||||
|
|
||||||
#### WPSCAN EXAMPLES
|
#### WPSCAN EXAMPLES
|
||||||
|
|
||||||
|
|||||||
19
dev/stats.rb
Executable file
19
dev/stats.rb
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# encoding: UTF-8
|
||||||
|
|
||||||
|
require File.dirname(__FILE__) + '/../lib/wpscan/wpscan_helper'
|
||||||
|
|
||||||
|
wordpress_json = json(WORDPRESSES_FILE)
|
||||||
|
plugins_json = json(PLUGINS_FILE)
|
||||||
|
themes_json = json(THEMES_FILE)
|
||||||
|
|
||||||
|
puts 'WPScan Database Statistics:'
|
||||||
|
puts "* Total tracked wordpresses: #{wordpress_json.count}"
|
||||||
|
puts "* Total tracked plugins: #{plugins_json.count}"
|
||||||
|
puts "* Total tracked themes: #{themes_json.count}"
|
||||||
|
puts "* Total vulnerable wordpresses: #{wordpress_json.select { |item| !wordpress_json[item]['vulnerabilities'].empty? }.count}"
|
||||||
|
puts "* Total vulnerable plugins: #{plugins_json.select { |item| !plugins_json[item]['vulnerabilities'].empty? }.count}"
|
||||||
|
puts "* Total vulnerable themes: #{themes_json.select { |item| !themes_json[item]['vulnerabilities'].empty? }.count}"
|
||||||
|
puts "* Total wordpress vulnerabilities: #{}"
|
||||||
|
puts "* Total plugin vulnerabilities: #{}"
|
||||||
|
puts "* Total theme vulnerabilities: #{}"
|
||||||
@@ -143,8 +143,8 @@ class Browser
|
|||||||
end
|
end
|
||||||
|
|
||||||
params.merge!(referer: referer)
|
params.merge!(referer: referer)
|
||||||
params.merge!(timeout: @request_timeout) if @request_timeout
|
params.merge!(timeout: @request_timeout) if @request_timeout && !params.key?(:timeout)
|
||||||
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout
|
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout && !params.key?(:connecttimeout)
|
||||||
|
|
||||||
# Used to enable the cache system if :cache_ttl > 0
|
# Used to enable the cache system if :cache_ttl > 0
|
||||||
params.merge!(cache_ttl: @cache_ttl) unless params.key?(:cache_ttl)
|
params.merge!(cache_ttl: @cache_ttl) unless params.key?(:cache_ttl)
|
||||||
@@ -153,8 +153,8 @@ class Browser
|
|||||||
params.merge!(maxredirs: 3) unless params.key?(:maxredirs)
|
params.merge!(maxredirs: 3) unless params.key?(:maxredirs)
|
||||||
|
|
||||||
# Disable SSL-Certificate checks
|
# Disable SSL-Certificate checks
|
||||||
params.merge!(ssl_verifypeer: false)
|
params.merge!(ssl_verifypeer: false) unless params.key?(:ssl_verifypeer)
|
||||||
params.merge!(ssl_verifyhost: 0)
|
params.merge!(ssl_verifyhost: 0) unless params.key?(:ssl_verifyhost)
|
||||||
|
|
||||||
params.merge!(cookiejar: @cache_dir + '/cookie-jar')
|
params.merge!(cookiejar: @cache_dir + '/cookie-jar')
|
||||||
params.merge!(cookiefile: @cache_dir + '/cookie-jar')
|
params.merge!(cookiefile: @cache_dir + '/cookie-jar')
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
class Browser
|
class Browser
|
||||||
module Options
|
module Options
|
||||||
|
|
||||||
attr_accessor :cache_ttl, :request_timeout, :connect_timeout
|
attr_accessor :request_timeout, :connect_timeout
|
||||||
attr_reader :basic_auth, :proxy, :proxy_auth, :throttle
|
attr_reader :basic_auth, :cache_ttl, :proxy, :proxy_auth, :throttle
|
||||||
attr_writer :user_agent
|
attr_writer :user_agent
|
||||||
|
|
||||||
# Sets the Basic Authentification credentials
|
# Sets the Basic Authentification credentials
|
||||||
@@ -25,6 +25,10 @@ class Browser
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cache_ttl=(ttl)
|
||||||
|
@cache_ttl = ttl.to_i
|
||||||
|
end
|
||||||
|
|
||||||
# @return [ Integer ]
|
# @return [ Integer ]
|
||||||
def max_threads
|
def max_threads
|
||||||
@max_threads || 1
|
@max_threads || 1
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ class CacheFileStore
|
|||||||
@storage_path = File.expand_path(File.join(storage_path, storage_dir))
|
@storage_path = File.expand_path(File.join(storage_path, storage_dir))
|
||||||
@serializer = serializer
|
@serializer = serializer
|
||||||
|
|
||||||
# File.directory? for ruby <= 1.9 otherwise,
|
unless Dir.exist?(@storage_path)
|
||||||
# it makes more sense to do Dir.exist? :/
|
|
||||||
unless File.directory?(@storage_path)
|
|
||||||
FileUtils.mkdir_p(@storage_path)
|
FileUtils.mkdir_p(@storage_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -51,7 +49,7 @@ class CacheFileStore
|
|||||||
end
|
end
|
||||||
|
|
||||||
def write_entry(key, data_to_store, cache_ttl)
|
def write_entry(key, data_to_store, cache_ttl)
|
||||||
if cache_ttl > 0
|
if cache_ttl && cache_ttl > 0
|
||||||
File.open(get_entry_file_path(key), 'w') do |f|
|
File.open(get_entry_file_path(key), 'w') do |f|
|
||||||
begin
|
begin
|
||||||
f.write(@serializer.dump(data_to_store))
|
f.write(@serializer.dump(data_to_store))
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ 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')
|
LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update')
|
||||||
|
|
||||||
WPSCAN_VERSION = '2.9'
|
MIN_RUBY_VERSION = '2.1.9'
|
||||||
|
|
||||||
|
WPSCAN_VERSION = '2.9.1'
|
||||||
|
|
||||||
$LOAD_PATH.unshift(LIB_DIR)
|
$LOAD_PATH.unshift(LIB_DIR)
|
||||||
$LOAD_PATH.unshift(WPSCAN_LIB_DIR)
|
$LOAD_PATH.unshift(WPSCAN_LIB_DIR)
|
||||||
@@ -42,6 +44,11 @@ def kali_linux?
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Determins if installed on Windows OS
|
||||||
|
def windows?
|
||||||
|
Gem.win_platform?
|
||||||
|
end
|
||||||
|
|
||||||
require 'environment'
|
require 'environment'
|
||||||
|
|
||||||
def escape_glob(s)
|
def escape_glob(s)
|
||||||
@@ -223,8 +230,12 @@ end
|
|||||||
#
|
#
|
||||||
# @return [ Integer ] The number of lines in the given file
|
# @return [ Integer ] The number of lines in the given file
|
||||||
def count_file_lines(file)
|
def count_file_lines(file)
|
||||||
|
if windows?
|
||||||
|
`findstr /R /N "^" #{file.shellescape} | find /C ":"`.split[0].to_i
|
||||||
|
else
|
||||||
`wc -l #{file.shellescape}`.split[0].to_i
|
`wc -l #{file.shellescape}`.split[0].to_i
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Truncates a string to a specific length and adds ... at the end
|
# Truncates a string to a specific length and adds ... at the end
|
||||||
def truncate(input, size, trailing = '...')
|
def truncate(input, size, trailing = '...')
|
||||||
@@ -257,3 +268,7 @@ end
|
|||||||
def directory_listing_enabled?(url)
|
def directory_listing_enabled?(url)
|
||||||
Browser.get(url.to_s).body[%r{<title>Index of}] ? true : false
|
Browser.get(url.to_s).body[%r{<title>Index of}] ? true : false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def url_encode(str)
|
||||||
|
CGI.escape(str).gsub("+", "%20")
|
||||||
|
end
|
||||||
|
|||||||
@@ -22,13 +22,15 @@ class DbUpdater
|
|||||||
{
|
{
|
||||||
ssl_verifyhost: 2,
|
ssl_verifyhost: 2,
|
||||||
ssl_verifypeer: true,
|
ssl_verifypeer: true,
|
||||||
accept_encoding: 'gzip, deflate'
|
accept_encoding: 'gzip, deflate',
|
||||||
|
timeout: 300,
|
||||||
|
connecttimeout: 20
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ] The raw file URL associated with the given filename
|
# @return [ String ] The raw file URL associated with the given filename
|
||||||
def remote_file_url(filename)
|
def remote_file_url(filename)
|
||||||
"https://wpvulndb.com/data/#{filename}"
|
"https://data.wpscan.org/#{filename}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ] The checksum of the associated remote filename
|
# @return [ String ] The checksum of the associated remote filename
|
||||||
@@ -99,7 +101,7 @@ class DbUpdater
|
|||||||
puts " [i] Database File Checksum : #{db_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 (local: #{dl_checksum} remote: #{db_checksum})"
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
puts ' [i] Restoring Backup due to error' if verbose
|
puts ' [i] Restoring Backup due to error' if verbose
|
||||||
|
|||||||
@@ -1,35 +1,5 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
# Since ruby 1.9.2, URI::escape is obsolete
|
|
||||||
# See http://rosettacode.org/wiki/URL_encoding#Ruby and http://www.ruby-forum.com/topic/207489
|
|
||||||
if RUBY_VERSION >= '1.9.2'
|
|
||||||
module URI
|
|
||||||
extend self
|
|
||||||
|
|
||||||
def escape(str)
|
|
||||||
URI::Parser.new.escape(str)
|
|
||||||
end
|
|
||||||
alias :encode :escape
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if RUBY_VERSION < '1.9'
|
|
||||||
class Array
|
|
||||||
# Fix for grep with symbols in ruby <= 1.8.7
|
|
||||||
def _grep_(regexp)
|
|
||||||
matches = []
|
|
||||||
self.each do |value|
|
|
||||||
value = value.to_s
|
|
||||||
matches << value if value.match(regexp)
|
|
||||||
end
|
|
||||||
matches
|
|
||||||
end
|
|
||||||
|
|
||||||
alias_method :grep, :_grep_
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This is used in WpItem::Existable
|
# This is used in WpItem::Existable
|
||||||
module Typhoeus
|
module Typhoeus
|
||||||
class Response
|
class Response
|
||||||
@@ -101,8 +71,8 @@ end
|
|||||||
class Numeric
|
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(abs)/Math.log(1024)).floor
|
||||||
s = '%.3f' % (to_f / 1024**e)
|
s = '%.3f' % (abs.to_f / 1024**e)
|
||||||
s.sub(/\.?0*$/, ' ' + units[e])
|
s.sub(/\.?0*$/, ' ' + units[e])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -100,9 +100,7 @@ class WpItem
|
|||||||
#
|
#
|
||||||
# @return [ void ]
|
# @return [ void ]
|
||||||
def path=(path)
|
def path=(path)
|
||||||
@path = URI.encode(
|
@path = path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
|
||||||
path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ WpItem ] other
|
# @param [ WpItem ] other
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class WpPlugin < WpItem
|
|||||||
#
|
#
|
||||||
# @return [ void ]
|
# @return [ void ]
|
||||||
def forge_uri(target_base_uri)
|
def forge_uri(target_base_uri)
|
||||||
@uri = target_base_uri.merge(URI.encode(wp_plugins_dir + '/' + name + '/'))
|
@uri = target_base_uri.merge("#{wp_plugins_dir}/#{url_encode(name)}/")
|
||||||
end
|
end
|
||||||
|
|
||||||
def db_file
|
def db_file
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class WpTheme < WpItem
|
|||||||
#
|
#
|
||||||
# @return [ void ]
|
# @return [ void ]
|
||||||
def forge_uri(target_base_uri)
|
def forge_uri(target_base_uri)
|
||||||
@uri = target_base_uri.merge(URI.encode(wp_content_dir + '/themes/' + name + '/'))
|
@uri = target_base_uri.merge("#{wp_content_dir}/themes/#{url_encode(name)}/")
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ] The url to the theme stylesheet
|
# @return [ String ] The url to the theme stylesheet
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class WpTheme
|
|||||||
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}" if 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
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
class WpUser < WpItem
|
class WpUser < WpItem
|
||||||
module BruteForcable
|
module BruteForcable
|
||||||
|
|
||||||
|
attr_reader :progress_bar
|
||||||
|
|
||||||
# Brute force the user with the wordlist supplied
|
# Brute force the user with the wordlist supplied
|
||||||
#
|
#
|
||||||
# It can take a long time to queue 2 million requests,
|
# It can take a long time to queue 2 million requests,
|
||||||
@@ -25,7 +27,8 @@ class WpUser < WpItem
|
|||||||
hydra = browser.hydra
|
hydra = browser.hydra
|
||||||
queue_count = 0
|
queue_count = 0
|
||||||
found = false
|
found = false
|
||||||
progress_bar = self.progress_bar(count_file_lines(wordlist)+1, options)
|
|
||||||
|
create_progress_bar(count_file_lines(wordlist)+1, options)
|
||||||
|
|
||||||
File.open(wordlist).each do |password|
|
File.open(wordlist).each do |password|
|
||||||
password.chomp!
|
password.chomp!
|
||||||
@@ -42,7 +45,7 @@ class WpUser < WpItem
|
|||||||
request.on_complete do |response|
|
request.on_complete do |response|
|
||||||
progress_bar.progress += 1 if options[:show_progression] && !found
|
progress_bar.progress += 1 if options[:show_progression] && !found
|
||||||
|
|
||||||
puts "\n Trying Username : #{login} Password : #{password}" if options[:verbose]
|
progress_bar.log(" Trying Username: #{login} Password: #{password}") if options[:verbose]
|
||||||
|
|
||||||
if valid_password?(response, password, redirect_url, options)
|
if valid_password?(response, password, redirect_url, options)
|
||||||
found = true
|
found = true
|
||||||
@@ -57,7 +60,7 @@ class WpUser < WpItem
|
|||||||
if queue_count >= browser.max_threads
|
if queue_count >= browser.max_threads
|
||||||
hydra.run
|
hydra.run
|
||||||
queue_count = 0
|
queue_count = 0
|
||||||
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
|
progress_bar.log(" Sent #{browser.max_threads} request/s ...") if options[:verbose]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -71,9 +74,9 @@ class WpUser < WpItem
|
|||||||
#
|
#
|
||||||
# @return [ ProgressBar ]
|
# @return [ ProgressBar ]
|
||||||
# :nocov:
|
# :nocov:
|
||||||
def progress_bar(passwords_size, options)
|
def create_progress_bar(passwords_size, options)
|
||||||
if options[:show_progression]
|
if options[:show_progression]
|
||||||
ProgressBar.create(
|
@progress_bar = ProgressBar.create(
|
||||||
format: '%t %a <%B> (%c / %C) %P%% %e',
|
format: '%t %a <%B> (%c / %C) %P%% %e',
|
||||||
title: " Brute Forcing '#{login}'",
|
title: " Brute Forcing '#{login}'",
|
||||||
total: passwords_size
|
total: passwords_size
|
||||||
@@ -107,20 +110,20 @@ class WpUser < WpItem
|
|||||||
progression = "#{info('[SUCCESS]')} Login : #{login} Password : #{password}\n\n"
|
progression = "#{info('[SUCCESS]')} Login : #{login} Password : #{password}\n\n"
|
||||||
valid = true
|
valid = true
|
||||||
elsif response.body =~ /login_error/i
|
elsif response.body =~ /login_error/i
|
||||||
verbose = "\n Incorrect login and/or password."
|
verbose = "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? (#{response.return_message})")
|
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 or use the --throttle option.')
|
||||||
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
|
||||||
|
|
||||||
puts "\n " + progression if progression && options[:show_progression]
|
progress_bar.log(" #{progression}") if progression && options[:show_progression]
|
||||||
puts verbose if verbose && options[:verbose]
|
progress_bar.log(" #{verbose}") if verbose && options[:verbose]
|
||||||
|
|
||||||
valid || false
|
valid || false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class WpVersion < WpItem
|
|||||||
include WpVersion::Output
|
include WpVersion::Output
|
||||||
|
|
||||||
# The version number
|
# The version number
|
||||||
attr_accessor :number
|
attr_accessor :number, :metadata
|
||||||
alias_method :version, :number # Needed to have the right behaviour in Vulnerable#vulnerable_to?
|
alias_method :version, :number # Needed to have the right behaviour in Vulnerable#vulnerable_to?
|
||||||
|
|
||||||
# @return [ Array ]
|
# @return [ Array ]
|
||||||
@@ -35,4 +35,14 @@ class WpVersion < WpItem
|
|||||||
a << node.text.to_s
|
a << node.text.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ] Metadata for specific WP version from WORDPRESSES_FILE
|
||||||
|
def metadata(version)
|
||||||
|
json = json(db_file)
|
||||||
|
|
||||||
|
metadata = {}
|
||||||
|
metadata[:release_date] = json[version]['release_date']
|
||||||
|
metadata[:changelog_url] = json[version]['changelog_url']
|
||||||
|
metadata
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -114,34 +114,6 @@ class WpVersion < WpItem
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_from_stylesheets_numbers(target_uri)
|
|
||||||
wp_versions = WpVersion.all
|
|
||||||
found = {}
|
|
||||||
pattern = /\bver=([0-9\.]+)/i
|
|
||||||
|
|
||||||
Nokogiri::HTML(Browser.get(target_uri.to_s).body).css('link,script').each do |tag|
|
|
||||||
%w(href src).each do |attribute|
|
|
||||||
attr_value = tag.attribute(attribute).to_s
|
|
||||||
|
|
||||||
next if attr_value.nil? || attr_value.empty?
|
|
||||||
|
|
||||||
uri = Addressable::URI.parse(attr_value)
|
|
||||||
next unless uri.query && uri.query.match(pattern)
|
|
||||||
|
|
||||||
version = Regexp.last_match[1].to_s
|
|
||||||
|
|
||||||
found[version] ||= 0
|
|
||||||
found[version] += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
found.delete_if { |v, _| !wp_versions.include?(v) }
|
|
||||||
|
|
||||||
best_guess = found.sort_by(&:last).last
|
|
||||||
# best_guess[0]: version number, [1] numbers of occurences
|
|
||||||
best_guess && best_guess[1] > 1 ? best_guess[0] : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Uses data/wp_versions.xml to try to identify a
|
# Uses data/wp_versions.xml to try to identify a
|
||||||
# wordpress version.
|
# wordpress version.
|
||||||
#
|
#
|
||||||
@@ -158,8 +130,6 @@ class WpVersion < WpItem
|
|||||||
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||||
xml = xml(versions_xml)
|
xml = xml(versions_xml)
|
||||||
|
|
||||||
# This wp_item will take care of encoding the path
|
|
||||||
# and replace variables like $wp-content$ & $wp-plugins$
|
|
||||||
wp_item = WpItem.new(target_uri,
|
wp_item = WpItem.new(target_uri,
|
||||||
wp_content_dir: wp_content_dir,
|
wp_content_dir: wp_content_dir,
|
||||||
wp_plugins_dir: wp_plugins_dir)
|
wp_plugins_dir: wp_plugins_dir)
|
||||||
@@ -218,5 +188,32 @@ class WpVersion < WpItem
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_from_stylesheets_numbers(target_uri)
|
||||||
|
wp_versions = WpVersion.all
|
||||||
|
found = {}
|
||||||
|
pattern = /\bver=([0-9\.]+)/i
|
||||||
|
|
||||||
|
Nokogiri::HTML(Browser.get(target_uri.to_s).body).css('link,script').each do |tag|
|
||||||
|
%w(href src).each do |attribute|
|
||||||
|
attr_value = tag.attribute(attribute).to_s
|
||||||
|
|
||||||
|
next if attr_value.nil? || attr_value.empty?
|
||||||
|
|
||||||
|
uri = Addressable::URI.parse(attr_value)
|
||||||
|
next unless uri.query && uri.query.match(pattern)
|
||||||
|
|
||||||
|
version = Regexp.last_match[1].to_s
|
||||||
|
|
||||||
|
found[version] ||= 0
|
||||||
|
found[version] += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
found.delete_if { |v, _| !wp_versions.include?(v) }
|
||||||
|
|
||||||
|
best_guess = found.sort_by(&:last).last
|
||||||
|
# best_guess[0]: version number, [1] numbers of occurences
|
||||||
|
best_guess && best_guess[1] > 1 ? best_guess[0] : nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,8 +4,16 @@ class WpVersion < WpItem
|
|||||||
module Output
|
module Output
|
||||||
|
|
||||||
def output(verbose = false)
|
def output(verbose = false)
|
||||||
|
metadata = self.metadata(self.number)
|
||||||
|
|
||||||
puts
|
puts
|
||||||
|
if verbose
|
||||||
puts info("WordPress version #{self.number} identified from #{self.found_from}")
|
puts info("WordPress version #{self.number} identified from #{self.found_from}")
|
||||||
|
puts " | Released: #{metadata[:release_date]}"
|
||||||
|
puts " | Changelog: #{metadata[:changelog_url]}"
|
||||||
|
else
|
||||||
|
puts info("WordPress version #{self.number} identified from #{self.found_from} #{"(Released on #{metadata[:release_date]})" if metadata[:release_date]}")
|
||||||
|
end
|
||||||
|
|
||||||
vulnerabilities = self.vulnerabilities
|
vulnerabilities = self.vulnerabilities
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
|
|
||||||
version = RUBY_VERSION.dup
|
version = RUBY_VERSION.dup
|
||||||
if Gem::Version.create(version) < Gem::Version.create(1.9)
|
|
||||||
puts "Ruby >= 1.9 required to run wpscan (You have #{version})"
|
if Gem::Version.create(version) < Gem::Version.create(MIN_RUBY_VERSION)
|
||||||
|
puts "Ruby >= #{MIN_RUBY_VERSION} required to run wpscan (You have #{version})"
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ begin
|
|||||||
require 'shellwords'
|
require 'shellwords'
|
||||||
require 'fileutils'
|
require 'fileutils'
|
||||||
require 'pathname'
|
require 'pathname'
|
||||||
|
require 'cgi'
|
||||||
# Third party libs
|
# Third party libs
|
||||||
require 'typhoeus'
|
require 'typhoeus'
|
||||||
require 'yajl/json_gem'
|
require 'yajl/json_gem'
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ class WpTarget < WebSite
|
|||||||
@uri.merge("#{wp_content_dir}/uploads/").to_s
|
@uri.merge("#{wp_content_dir}/uploads/").to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def includes_dir_url
|
||||||
|
@uri.merge("wp-includes/").to_s
|
||||||
|
end
|
||||||
|
|
||||||
# Script for replacing strings in wordpress databases
|
# Script for replacing strings in wordpress databases
|
||||||
# reveals database credentials after hitting submit
|
# reveals database credentials after hitting submit
|
||||||
# http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
|
# http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
|
||||||
@@ -153,4 +158,8 @@ class WpTarget < WebSite
|
|||||||
def upload_directory_listing_enabled?
|
def upload_directory_listing_enabled?
|
||||||
directory_listing_enabled?(upload_dir_url)
|
directory_listing_enabled?(upload_dir_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def include_directory_listing_enabled?
|
||||||
|
directory_listing_enabled?(includes_dir_url)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class WpTarget < WebSite
|
|||||||
queue_count = 0
|
queue_count = 0
|
||||||
|
|
||||||
backups.each do |file|
|
backups.each do |file|
|
||||||
file_url = @uri.merge(URI.escape(file)).to_s
|
file_url = @uri.merge(url_encode(file)).to_s
|
||||||
request = browser.forge_request(file_url)
|
request = browser.forge_request(file_url)
|
||||||
|
|
||||||
request.on_complete do |response|
|
request.on_complete do |response|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ def help
|
|||||||
puts
|
puts
|
||||||
puts 'Some values are settable in a config file, see the example.conf.json'
|
puts 'Some values are settable in a config file, see the example.conf.json'
|
||||||
puts
|
puts
|
||||||
puts '--update Update to the database to the latest version.'
|
puts '--update Update the database to the latest version.'
|
||||||
puts '--url | -u <target url> The WordPress URL/domain to scan.'
|
puts '--url | -u <target url> The WordPress URL/domain to scan.'
|
||||||
puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
|
puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
|
||||||
puts '--enumerate | -e [option(s)] Enumeration.'
|
puts '--enumerate | -e [option(s)] Enumeration.'
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ describe 'WpTimthumbs::Detectable' do
|
|||||||
|
|
||||||
def expected_targets_from_theme(theme_name)
|
def expected_targets_from_theme(theme_name)
|
||||||
expected = []
|
expected = []
|
||||||
%w{
|
%w(
|
||||||
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
||||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
|
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
|
||||||
}.each do |file|
|
).each do |file|
|
||||||
path = "$wp-content$/themes/#{theme_name}/#{file}"
|
path = "$wp-content$/themes/#{theme_name}/#{file}"
|
||||||
expected << WpTimthumb.new(uri, path: path)
|
expected << WpTimthumb.new(uri, path: path)
|
||||||
end
|
end
|
||||||
@@ -46,7 +46,7 @@ describe 'WpTimthumbs::Detectable' do
|
|||||||
after do
|
after do
|
||||||
targets = subject.send(:targets_items_from_file, file, wp_target)
|
targets = subject.send(:targets_items_from_file, file, wp_target)
|
||||||
|
|
||||||
expect(targets.map { |t| t.url }).to eq @expected.map { |t| t.url }
|
expect(targets.map(&:url)).to eq @expected.map(&:url)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when an empty file' do
|
context 'when an empty file' do
|
||||||
@@ -71,7 +71,7 @@ describe 'WpTimthumbs::Detectable' do
|
|||||||
theme = 'hello-world'
|
theme = 'hello-world'
|
||||||
targets = subject.send(:theme_timthumbs, theme, wp_target)
|
targets = subject.send(:theme_timthumbs, theme, wp_target)
|
||||||
|
|
||||||
expect(targets.map { |t| t.url }).to eq expected_targets_from_theme(theme).map { |t| t.url }
|
expect(targets.map(&:url)).to eq expected_targets_from_theme(theme).map(&:url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ describe 'WpTimthumbs::Detectable' do
|
|||||||
after do
|
after do
|
||||||
targets = subject.send(:targets_items, wp_target, options)
|
targets = subject.send(:targets_items, wp_target, options)
|
||||||
|
|
||||||
expect(targets.map { |t| t.url }).to eq @expected.sort.map { |t| t.url }
|
expect(targets.map(&:url)).to match_array(@expected.map(&:url))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no :theme_name' do
|
context 'when no :theme_name' do
|
||||||
|
|||||||
@@ -105,11 +105,6 @@ describe WpItem do
|
|||||||
@expected = 'plugins/readme.txt'
|
@expected = 'plugins/readme.txt'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'also encodes chars' do
|
|
||||||
@path = 'some dir with spaces'
|
|
||||||
@expected = 'some%20dir%20with%20spaces'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#uri' do
|
describe '#uri' do
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ shared_examples 'WpTarget::WpConfigBackup' do
|
|||||||
# set all @config_backup_files to point to a 404
|
# set all @config_backup_files to point to a 404
|
||||||
before :each do
|
before :each do
|
||||||
config_backup_files.each do |backup_file|
|
config_backup_files.each do |backup_file|
|
||||||
file_url = wp_target.uri.merge(URI.escape(backup_file)).to_s
|
file_url = wp_target.uri.merge(url_encode(backup_file)).to_s
|
||||||
|
|
||||||
stub_request(:get, file_url).to_return(status: 404)
|
stub_request(:get, file_url).to_return(status: 404)
|
||||||
end
|
end
|
||||||
@@ -24,7 +24,7 @@ shared_examples 'WpTarget::WpConfigBackup' do
|
|||||||
expected = []
|
expected = []
|
||||||
|
|
||||||
config_backup_files.sample(1).each do |backup_file|
|
config_backup_files.sample(1).each do |backup_file|
|
||||||
file_url = wp_target.uri.merge(URI.escape(backup_file)).to_s
|
file_url = wp_target.uri.merge(url_encode(backup_file)).to_s
|
||||||
expected << file_url
|
expected << file_url
|
||||||
|
|
||||||
stub_request_to_fixture(url: file_url, fixture: fixtures_dir + '/wp-config.php')
|
stub_request_to_fixture(url: file_url, fixture: fixtures_dir + '/wp-config.php')
|
||||||
@@ -40,7 +40,7 @@ shared_examples 'WpTarget::WpConfigBackup' do
|
|||||||
expected = []
|
expected = []
|
||||||
|
|
||||||
config_backup_files.sample(2).each do |backup_file|
|
config_backup_files.sample(2).each do |backup_file|
|
||||||
file_url = wp_target.uri.merge(URI.escape(backup_file)).to_s
|
file_url = wp_target.uri.merge(url_encode(backup_file)).to_s
|
||||||
expected << file_url
|
expected << file_url
|
||||||
|
|
||||||
stub_request_to_fixture(url: file_url, fixture: fixtures_dir + '/wp-config.php')
|
stub_request_to_fixture(url: file_url, fixture: fixtures_dir + '/wp-config.php')
|
||||||
|
|||||||
18
wpscan.rb
18
wpscan.rb
@@ -82,6 +82,10 @@ def main
|
|||||||
|
|
||||||
wp_target = WpTarget.new(wpscan_options.url, wpscan_options.to_h)
|
wp_target = WpTarget.new(wpscan_options.url, wpscan_options.to_h)
|
||||||
|
|
||||||
|
if wp_target.wordpress_hosted?
|
||||||
|
raise 'We do not support scanning *.wordpress.com hosted blogs'
|
||||||
|
end
|
||||||
|
|
||||||
# Remote website up?
|
# Remote website up?
|
||||||
unless wp_target.online?
|
unless wp_target.online?
|
||||||
raise "The WordPress URL supplied '#{wp_target.uri}' seems to be down."
|
raise "The WordPress URL supplied '#{wp_target.uri}' seems to be down."
|
||||||
@@ -152,15 +156,11 @@ def main
|
|||||||
|
|
||||||
# Output runtime data
|
# Output runtime data
|
||||||
start_time = Time.now
|
start_time = Time.now
|
||||||
start_memory = get_memory_usage
|
start_memory = get_memory_usage unless windows?
|
||||||
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?
|
|
||||||
puts critical('We do not support scanning *.wordpress.com hosted blogs')
|
|
||||||
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}'")
|
||||||
|
|
||||||
@@ -221,6 +221,10 @@ def main
|
|||||||
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
|
||||||
|
|
||||||
|
if wp_target.include_directory_listing_enabled?
|
||||||
|
puts warning("Includes directory has directory listing enabled: #{wp_target.includes_dir_url}")
|
||||||
|
end
|
||||||
|
|
||||||
enum_options = {
|
enum_options = {
|
||||||
show_progression: true,
|
show_progression: true,
|
||||||
exclude_content: wpscan_options.exclude_content_based
|
exclude_content: wpscan_options.exclude_content_based
|
||||||
@@ -440,12 +444,12 @@ def main
|
|||||||
|
|
||||||
stop_time = Time.now
|
stop_time = Time.now
|
||||||
elapsed = stop_time - start_time
|
elapsed = stop_time - start_time
|
||||||
used_memory = get_memory_usage - start_memory
|
used_memory = get_memory_usage - start_memory unless windows?
|
||||||
|
|
||||||
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}") unless windows?
|
||||||
puts info("Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}")
|
puts info("Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}")
|
||||||
|
|
||||||
rescue Interrupt
|
rescue Interrupt
|
||||||
|
|||||||
Reference in New Issue
Block a user