Compare commits

..

106 Commits
2.8 ... 2.9.1

Author SHA1 Message Date
Christian Mehlmauer
f832e27b49 correct stats an correct data files 2016-05-06 11:52:05 +02:00
ethicalhack3r
6ce29f73c5 Update with correct stat #935 2016-05-06 11:35:57 +02:00
ethicalhack3r
920338fb62 Prepare 2.9.1 release #935 2016-05-06 00:15:53 +02:00
Christian Mehlmauer
49d0a9e6d9 check directory listing in wp-includes 2016-05-05 00:01:52 +02:00
Christian Mehlmauer
fe401e622b add stats 2016-05-04 23:09:00 +02:00
Christian Mehlmauer
6e32cb0db2 changelog 2016-05-04 22:46:02 +02:00
Ryan Dewhurst
73171eb39d Merge pull request #929 from wpscanteam/wp_metadata
WP Metadata Integration
2016-04-28 14:35:43 +02:00
ethicalhack3r
2e05f4171e Update to Ruby 2.3.1 2016-04-28 14:04:54 +02:00
Christian Mehlmauer
75b8c303e2 more verbose error 2016-04-27 15:19:07 +02:00
Christian Mehlmauer
bd7a493f1c travis errors 2016-04-20 20:49:17 +02:00
Christian Mehlmauer
9dada7c8f4 travis errors 2016-04-20 20:41:46 +02:00
ethicalhack3r
fe7aede458 Better output 2016-04-20 13:39:05 +02:00
ethicalhack3r
cdf2b38780 Only show changelog if verbose 2016-04-20 13:09:02 +02:00
ethicalhack3r
a09dbab6a8 Use db_file 2016-04-20 12:43:56 +02:00
ethicalhack3r
49a6d275d2 Update comment 2016-04-20 12:37:46 +02:00
ethicalhack3r
8192a4a215 Fix typo 2016-04-20 12:27:09 +02:00
ethicalhack3r
1d6593fd4d Add WP metadata #704 2016-04-20 12:02:15 +02:00
Christian Mehlmauer
bf99e31e70 higher update timeout 2016-04-20 09:33:56 +02:00
Christian Mehlmauer
5386496bdc move wordpress check to the top 2016-04-06 14:13:56 +02:00
Christian Mehlmauer
6451510449 new ruby version with security bugfixes released 2016-04-03 00:34:52 +02:00
Christian Mehlmauer
cd68aa719c possible fix for timeouts 2016-04-01 11:52:13 +02:00
Christian Mehlmauer
b328dc4ff9 possible fix for #912 2016-03-11 09:28:42 +01:00
Christian Mehlmauer
1e1c79aa56 Merge pull request #909 from wpscanteam/ruby_version
drop ruby 1.9 and 2.0 support, whitespaces
2016-02-26 14:08:38 +01:00
Christian Mehlmauer
08650ce156 fix travis 2016-02-25 06:39:47 +01:00
Christian Mehlmauer
a1929719f3 version 2.1.8 minimum requirement 2016-02-24 23:48:50 +01:00
Christian Mehlmauer
d34da72cd3 ruby 2.0.0 is EOL 2016-02-24 23:41:32 +01:00
Christian Mehlmauer
816b18b604 drop ruby 1.9 support, whitespaces 2016-02-23 18:07:20 +01:00
Christian Mehlmauer
a78a13bf3f revert change 2016-02-18 00:02:55 +01:00
Christian Mehlmauer
33f8aaf1dc Merge branch 'master' of github.com:wpscanteam/wpscan 2016-02-17 23:30:45 +01:00
Christian Mehlmauer
26ab95d822 more actual gems 2016-02-17 23:30:28 +01:00
erwanlr
cea01d8aa0 Improves brute forcer output to avoid confustions 2016-02-13 16:44:29 +00:00
Ryan Dewhurst
0e61f1e284 Merge pull request #901 from wpscanteam/new_urls
add new urls
2016-02-06 22:26:25 +01:00
Christian Mehlmauer
ddef061b90 add new urls 2016-02-05 22:25:18 +01:00
erwanlr
addeab8947 Fixes #900 2016-02-04 20:37:13 +01:00
erwanlr
55dc665404 Better specs 2016-01-11 16:33:29 +00:00
erwanlr
8f8538e9e9 Changes the order of the WP version from stylesheets check - Fixes #865 2016-01-11 16:27:22 +00:00
Christian Mehlmauer
348ca55bee copyright 2016-01-08 23:54:04 +01:00
Christian Mehlmauer
1bb5bc7f33 fix rspec 2016-01-03 21:28:02 +01:00
ethicalhack3r
3be5e1fcf5 Add Windows OS detection 2016-01-03 20:15:11 +01:00
Christian Mehlmauer
9df8cc9243 Update README.md 2016-01-02 10:57:55 +01:00
Christian Mehlmauer
e28c84aa34 Update fedore install instructions
See #886
2016-01-02 10:52:23 +01:00
Christian Mehlmauer
7db6b54761 Merge pull request #894 from nonmadden/update-ruby
Update to Ruby 2.3.0
2015-12-31 10:22:47 +01:00
nonmadden
e3a06f5694 Update to Ruby 2.3.0 2015-12-31 10:41:04 +07:00
erwanlr
7c5d15e098 Updates Nokogiri dep 2015-12-18 18:59:32 +01:00
ethicalhack3r
d683c0f151 Update to Ruby 2.2.4 2015-12-18 11:13:41 +01:00
erwanlr
1e67fa26ff Fixes #890 2015-11-26 14:12:04 +00:00
erwanlr
0ae6ef59ec Fixes an issue with --cache-ttl being a Strig instead of an integer 2015-11-26 13:52:12 +00:00
erwanlr
e27ef40e0f Updates Nokogiri dep version 2015-11-26 11:53:13 +00:00
ethicalhack3r
380760d028 Onlt shoe theme description when there is one 2015-10-26 16:06:13 +01:00
ethicalhack3r
18cfdafc19 Fix typo in options 2015-10-15 16:28:42 +02:00
ethicalhack3r
0934a2e329 Recommend RVM in readme 2015-10-15 15:51:38 +02:00
ethicalhack3r
d1a320324e Update reame CLI options 2015-10-15 15:49:18 +02:00
ethicalhack3r
361c96d746 Version 2.9 release 2015-10-15 13:01:53 +02:00
erwanlr
e7dbf9278d Fixes #873 - mu-plugins detection 2015-10-13 13:17:22 +01:00
erwanlr
6564fddb27 Adds a reminder about updating the terminal-table version 2015-10-13 13:12:12 +01:00
erwanlr
d382874e86 Fixes incorrect detection of the FDP data 2015-10-12 12:57:20 +01:00
erwanlr
91b30bee9f Updates Typhoeus dependency 2015-10-09 19:03:37 +02:00
erwanlr
7804aad776 Removes useless stuff & update the --throttle options text 2015-10-07 22:09:23 +01:00
erwanlr
b7552ac8aa Tried to throttle things 2015-10-07 19:03:52 +01:00
erwanlr
a76c94cccf Let's try Travis container-based infra & caching 2015-09-18 16:13:37 +02:00
Christian Mehlmauer
c0ae5c7cad Merge pull request #864 from wpscanteam/apiv2
new dependency
2015-09-11 21:09:51 +02:00
Christian Mehlmauer
cc55b39b83 new dependency 2015-09-11 15:31:29 +02:00
ethicalhack3r
d8a6884ab6 Only show 'up to date' string when version found 2015-09-09 15:46:44 +02:00
Ryan Dewhurst
5ce3581386 Merge pull request #862 from wpscanteam/apiv2
Apiv2
2015-09-08 21:00:03 +02:00
ethicalhack3r
2208f2a8c0 Implement lesser? method #862 2015-09-08 17:54:32 +02:00
ethicalhack3r
a4a14c7e63 Better version output #862 2015-09-08 17:24:10 +02:00
erwanlr
aa464b476c Fixes a bug where -e vp was displaying non vulnerable plugins - Ref #853 2015-09-06 15:25:29 +01:00
erwanlr
3c92712a6e Uses yajl as JSON parser to reduce memory used 2015-09-06 14:29:41 +01:00
erwanlr
fd0c47f5d7 Adds the latest_version, last_updated and popular? attributes - Ref #853 2015-09-06 14:26:36 +01:00
erwanlr
c03a44d225 Removes useless code 2015-09-06 13:32:13 +01:00
ethicalhack3r
d31d45ba71 Remove unneede newline 2015-09-05 14:10:08 +02:00
ethicalhack3r
db528b27f4 Implement Erwan's feedback #853 2015-09-05 13:49:03 +02:00
ethicalhack3r
e6d29f6f18 New json structure implemented #853 2015-09-03 22:04:44 +02:00
Christian Mehlmauer
e4d6b988ef forgot spec file, #858
Signed-off-by: Christian Mehlmauer <firefart@gmail.com>
2015-08-22 21:52:55 +02:00
Christian Mehlmauer
ec68291bf0 fix #858 2015-08-22 21:50:31 +02:00
ethicalhack3r
3a6a451db1 Update to Ruby 2.2.3 2015-08-21 09:41:06 +02:00
Christian Mehlmauer
7ec095d708 fix duplicate robots.txt entries 2015-08-18 15:55:10 +02:00
ethicalhack3r
57f6206aee Implement Erwan's feedbaxk #853 2015-08-14 21:51:55 +02:00
ethicalhack3r
390f10e83f Remove ArchAssault, 'had to close its doors' 2015-08-14 19:26:52 +02:00
ethicalhack3r
8727935cb2 Fix specs #853 2015-08-14 16:33:57 +02:00
ethicalhack3r
d0e868f556 Enable rspec fail-fast #853 2015-08-14 16:04:26 +02:00
ethicalhack3r
01c357e146 Fix specs #853 2015-08-14 16:03:21 +02:00
ethicalhack3r
a0fed4a9d0 Clean up last commit #853 2015-08-14 00:22:48 +02:00
ethicalhack3r
c4aed0ec89 Initial attempt at implementing apiv2 #853 2015-08-14 00:19:22 +02:00
erwanlr
cc737090a2 Fixes incorrect detection of the username 2015-08-13 10:27:33 +01:00
erwanlr
1652c09e95 Merge pull request #850 from mikicaivosevic/master
Re-factorises a statement
2015-08-12 14:53:43 +01:00
erwanlr
2538b88579 Adds the Accept-Encoding header when updating the DBs - Fixes #852 2015-08-12 14:50:14 +01:00
Mikica Ivosevic
8c2eb63840 update wp_target.rb
Refactor if else statement - wp_content_dir (credits: ethicalhack3r)
2015-07-28 12:41:09 +02:00
erwanlr
36df5ee6e4 Comments debug statement 2015-07-23 14:15:46 +01:00
erwanlr
9720b4edf1 Escapes brackets etc potentially present in Dir.pwd When using Dir.glob - Fixes #840 2015-07-23 14:15:04 +01:00
Christian Mehlmauer
13d35b7607 update email 2015-07-08 14:29:18 +02:00
Christian Mehlmauer
13c2c51cfd update email adress 2015-07-08 13:45:47 +02:00
ethicalhack3r
f43175b0c3 Use older terminal-table gem #841 2015-07-02 10:48:34 +02:00
erwanlr
1508aba8b2 Uses terminal-table 1.5.1 - Fixes #839 2015-06-28 13:54:25 +01:00
erwanlr
5414ab05e5 Restraints terminal-table version - Ref #839 2015-06-27 09:23:26 +01:00
erwanlr
bd5d2db634 Fixes #836 2015-06-26 09:24:17 +01:00
erwanlr
3259dd29d8 Merge pull request #833 from stefancastille/master
Adds a --vhost option (Virtualhost support)
2015-06-26 09:14:39 +01:00
stefancastille
6e56013a95 Update browser.rb 2015-06-25 16:18:04 +02:00
stefancastille
252f762209 Update wp_target.rb 2015-06-25 16:17:03 +02:00
stefancastille
15c0448cf1 Update wpscan_options.rb 2015-06-25 16:13:04 +02:00
erwanlr
4c800bacaa Fixes #835 2015-06-24 11:46:06 +01:00
stefancastille
86a73229c0 Update wp_target.rb 2015-06-17 08:46:14 +02:00
stefancastille
cc41b96e88 Update wpscan_options.rb 2015-06-17 08:44:50 +02:00
stefancastille
e16c5584d1 Update wpscan_options.rb 2015-06-17 08:44:04 +02:00
stefancastille
94bab3f550 Update wpscan_options.rb
Add support for virtual hosts
2015-06-17 08:42:59 +02:00
stefancastille
9d04b23fb2 Update browser.rb
add support for virtual hosts
2015-06-16 17:23:25 +02:00
85 changed files with 1942 additions and 1674 deletions

View File

@@ -1 +1 @@
2.2.2 2.3.1

View File

@@ -1,26 +1,22 @@
language: ruby language: ruby
sudo: false
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.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:
- wpscanteam@gmail.com - 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:

View File

@@ -1,6 +1,60 @@
# Changelog # Changelog
## Master ## Master
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.8...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
Released: 2015-10-15
New
* GZIP Encoding in updater
* Adds --throttle option to throttle requests
* Uses new API and local database file structure
* Adds last updated and latest version to plugins and themes
Removed
* ArchAssault from README
* APIv1 local databases
General core
* Update to Ruby 2.2.3
* Use yajl-ruby as JSON parser
* New dependancy for Ubuntu 14.04 (libgmp-dev)
* Use Travis container based infra and caching
Fixed issues
* Fix #835 - Readme requests to wp root dir
* Fix #836 - Critical icon output twice when the site is not running WP
* Fix #839 - Terminal-table dependency is broken
* Fix #841 - error: undefined method `cells' for #<Array:0x000000029cc2f8>
* Fix #852 - GZIP Encoding in updater
* Fix #853 - APIv2 integration
* Fix #858 - Detection FP
* Fix #873 - false positive "site has Must Use Plugins"
WPScan Database Statistics:
* Total vulnerable versions: 132
* Total vulnerable plugins: 1170
* Total vulnerable themes: 368
* Total version vulnerabilities: 1476
* Total plugin vulnerabilities: 1913
* Total theme vulnerabilities: 450
## Version 2.8 ## Version 2.8
Released: 2015-06-22 Released: 2015-06-22
@@ -100,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
@@ -456,4 +510,3 @@ Fixed issues
## Version 2.1 ## Version 2.1
Released 2013-3-4 Released 2013-3-4

View File

@@ -1,6 +1,6 @@
**CREDITS** **CREDITS**
This file is used to state the individual WPScan Team members (core developers) and give credit to WPScan's other contributors. If you feel your name should be in here email wpscanteam@gmail.com. This file is used to state the individual WPScan Team members (core developers) and give credit to WPScan's other contributors. If you feel your name should be in here email team@wpscan.org.
*WPScan Team* *WPScan Team*

11
Gemfile
View File

@@ -1,10 +1,13 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'typhoeus', '~>0.7.0' gem 'typhoeus', '>=0.8.0'
gem 'nokogiri' gem 'nokogiri', '>=1.6.7.1'
gem 'addressable' gem 'addressable'
gem 'json' gem 'yajl-ruby' # Better JSON parser regarding memory usage
gem 'terminal-table' # 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)
gem 'terminal-table', '~>1.4.5'
gem 'ruby-progressbar', '>=1.6.0' gem 'ruby-progressbar', '>=1.6.0'
group :test do group :test do

View File

@@ -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.
@@ -27,7 +27,7 @@ Example cases which do not require a commercial license, and thus fall under the
- Using WPScan to test your own systems. - Using WPScan to test your own systems.
- Any non-commercial use of WPScan. - Any non-commercial use of WPScan.
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - wpscanteam@gmail.com. If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan. We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.

100
README.md
View File

@@ -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.
@@ -38,7 +38,7 @@ Example cases which do not require a commercial license, and thus fall under the
- Using WPScan to test your own systems. - Using WPScan to test your own systems.
- Any non-commercial use of WPScan. - Any non-commercial use of WPScan.
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - wpscanteam@gmail.com. If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan. We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
@@ -88,12 +88,11 @@ WPScan comes pre-installed on the following Linux distributions:
- [Kali Linux](http://www.kali.org/) - [Kali Linux](http://www.kali.org/)
- [Pentoo](http://www.pentoo.ch/) - [Pentoo](http://www.pentoo.ch/)
- [SamuraiWTF](http://samurai.inguardians.com/) - [SamuraiWTF](http://samurai.inguardians.com/)
- [ArchAssault](https://archassault.org/)
- [BlackArch](http://blackarch.org/) - [BlackArch](http://blackarch.org/)
Prerequisites: Prerequisites:
- Ruby >= 1.9.2 - Recommended: 2.2.2 - 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
@@ -112,14 +111,14 @@ Before Ubuntu 14.04:
From Ubuntu 14.04: From Ubuntu 14.04:
sudo apt-get install libcurl4-openssl-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 libgmp-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
####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
@@ -127,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
@@ -150,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.2 rvm install 2.3.1
rvm use 2.2.2 --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
@@ -192,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
@@ -210,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
@@ -230,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

BIN
data.zip

Binary file not shown.

19
dev/stats.rb Executable file
View 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: #{}"

View File

@@ -17,14 +17,15 @@ class Browser
:proxy_auth, :proxy_auth,
:request_timeout, :request_timeout,
:connect_timeout, :connect_timeout,
:cookie :cookie,
:throttle
] ]
@@instance = nil @@instance = nil
attr_reader :hydra, :cache_dir attr_reader :hydra, :cache_dir
attr_accessor :referer, :cookie attr_accessor :referer, :cookie, :vhost
# @param [ Hash ] options # @param [ Hash ] options
# #
@@ -71,11 +72,13 @@ class Browser
# #
def browser_defaults def browser_defaults
@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
@request_timeout = 60 # 60s @request_timeout = 60 # 60s
@connect_timeout = 10 # 10s @connect_timeout = 10 # 10s
@user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)" @user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
@throttle = 0
end end
# #
@@ -86,7 +89,6 @@ class Browser
# #
# @return [ void ] # @return [ void ]
def load_config(config_file = nil) def load_config(config_file = nil)
if File.symlink?(config_file) if File.symlink?(config_file)
raise '[ERROR] Config file is a symlink.' raise '[ERROR] Config file is a symlink.'
else else
@@ -99,7 +101,6 @@ class Browser
self.send(:"#{option_name}=", data[option_name]) self.send(:"#{option_name}=", data[option_name])
end end
end end
end end
# @param [ String ] url # @param [ String ] url
@@ -121,11 +122,8 @@ class Browser
) )
if @proxy if @proxy
params = params.merge(proxy: @proxy) params.merge!(proxy: @proxy)
params.merge!(proxyauth: @proxy_auth) if @proxy_auth
if @proxy_auth
params = params.merge(proxyauth: @proxy_auth)
end
end end
if @basic_auth if @basic_auth
@@ -136,19 +134,27 @@ class Browser
) )
end end
if vhost
params = Browser.append_params_header_field(
params,
'Host',
vhost
)
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.has_key?(:cache_ttl) params.merge!(cache_ttl: @cache_ttl) unless params.key?(:cache_ttl)
# Prevent infinite self redirection # Prevent infinite self redirection
params.merge!(maxredirs: 3) unless params.has_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')
@@ -172,5 +178,4 @@ class Browser
end end
params params
end end
end end

View File

@@ -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 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
@@ -93,6 +97,11 @@ class Browser
@connect_timeout = timeout.to_i @connect_timeout = timeout.to_i
end end
# @param [ String, Integer ] throttle
def throttle=(throttle)
@throttle = throttle.to_i.abs / 1000.0
end
protected protected
def invalid_proxy_auth_format def invalid_proxy_auth_format
@@ -110,6 +119,5 @@ class Browser
end end
end end
end end
end end
end end

View File

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

View File

@@ -67,6 +67,7 @@ class WpItems < Array
end end
protected protected
# @return [ Class ] # @return [ Class ]
def item_class def item_class
Object.const_get(self.class.to_s.gsub(/.$/, '')) Object.const_get(self.class.to_s.gsub(/.$/, ''))

View File

@@ -32,11 +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)
unless results.include?(target_item) results << target_item unless results.include?(target_item)
if !options[:only_vulnerable] || options[:only_vulnerable] && target_item.vulnerable?
results << target_item
end
end
end end
end end
@@ -53,7 +49,7 @@ class WpItems < Array
# run the remaining requests # run the remaining requests
hydra.run hydra.run
results.select!(&:vulnerable?) if options[:only_vulnerable] results.select!(&:vulnerable?) if options[:type] == :vulnerable
results.sort! results.sort!
results # can't just return results.sort as it would return an array, and we want a WpItems results # can't just return results.sort as it would return an array, and we want a WpItems
@@ -155,15 +151,7 @@ class WpItems < Array
item_class = self.item_class item_class = self.item_class
vulns_file = self.vulns_file vulns_file = self.vulns_file
targets = vulnerable_targets_items(wp_target, item_class, vulns_file) targets = target_items_from_type(wp_target, item_class, vulns_file, options[:type])
unless options[:only_vulnerable]
unless options[:file]
raise 'A file must be supplied'
end
targets += targets_items_from_file(options[:file], wp_target, item_class, vulns_file)
end
targets.uniq! { |t| t.name } targets.uniq! { |t| t.name }
targets.sort_by { rand } targets.sort_by { rand }
@@ -174,14 +162,25 @@ class WpItems < Array
# @param [ String ] vulns_file # @param [ String ] vulns_file
# #
# @return [ Array<WpItem> ] # @return [ Array<WpItem> ]
def vulnerable_targets_items(wp_target, item_class, vulns_file) def target_items_from_type(wp_target, item_class, vulns_file, type)
targets = [] targets = []
json = json(vulns_file) json = json(vulns_file)
[*json].each do |item| case type
when :vulnerable
items = json.select { |item| !json[item]['vulnerabilities'].empty? }.keys
when :popular
items = json.select { |item| json[item]['popular'] == true }.keys
when :all
items = json.keys
else
raise "Unknown type #{type}"
end
items.each do |item|
targets << create_item( targets << create_item(
item_class, item_class,
item.keys.inject, item,
wp_target, wp_target,
vulns_file vulns_file
) )
@@ -233,6 +232,5 @@ class WpItems < Array
def item_class def item_class
Object.const_get(self.to_s.gsub(/.$/, '')) Object.const_get(self.to_s.gsub(/.$/, ''))
end end
end end
end end

View File

@@ -2,17 +2,11 @@
class WpPlugins < WpItems class WpPlugins < WpItems
module Detectable module Detectable
# @return [ String ] # @return [ String ]
def vulns_file def vulns_file
PLUGINS_VULNS_FILE PLUGINS_FILE
end end
# @return [ String ]
# def item_xpath
# '//plugin'
# end
# @param [ WpTarget ] wp_target # @param [ WpTarget ] wp_target
# @param [ Hash ] options # @param [ Hash ] options
# #

View File

@@ -5,13 +5,7 @@ class WpThemes < WpItems
# @return [ String ] # @return [ String ]
def vulns_file def vulns_file
THEMES_VULNS_FILE THEMES_FILE
end end
# @return [ String ]
# def item_xpath
# '//theme'
# end
end end
end end

View File

@@ -18,22 +18,19 @@ 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
# Data files # Data files
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.txt') WORDPRESSES_FILE = File.join(DATA_DIR, 'wordpresses.json')
PLUGINS_FULL_FILE = File.join(DATA_DIR, 'plugins_full.txt') PLUGINS_FILE = File.join(DATA_DIR, 'plugins.json')
PLUGINS_VULNS_FILE = File.join(DATA_DIR, 'plugin_vulns.json') THEMES_FILE = File.join(DATA_DIR, 'themes.json')
THEMES_FILE = File.join(DATA_DIR, 'themes.txt')
THEMES_FULL_FILE = File.join(DATA_DIR, 'themes_full.txt')
THEMES_VULNS_FILE = File.join(DATA_DIR, 'theme_vulns.json')
WP_VULNS_FILE = File.join(DATA_DIR, 'wp_vulns.json')
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml') WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml')
LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml') LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml')
# VULNS_XSD = File.join(DATA_DIR, 'vuln.xsd')
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') LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update')
WPSCAN_VERSION = '2.8' 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)
@@ -47,11 +44,20 @@ 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)
s.gsub(/[\\\{\}\[\]\*\?]/) { |x| '\\' + x }
end
# TODO : add an exclude pattern ? # TODO : add an exclude pattern ?
def require_files_from_directory(absolute_dir_path, files_pattern = '*.rb') def require_files_from_directory(absolute_dir_path, files_pattern = '*.rb')
files = Dir[File.join(absolute_dir_path, files_pattern)] files = Dir[File.join(escape_glob(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|
@@ -224,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 = '...')
@@ -258,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

View File

@@ -4,9 +4,8 @@
class DbUpdater class DbUpdater
FILES = %w( FILES = %w(
local_vulnerable_files.xml local_vulnerable_files.xsd local_vulnerable_files.xml local_vulnerable_files.xsd
plugins_full.txt plugins.txt themes_full.txt themes.txt
timthumbs.txt user-agents.txt wp_versions.xml wp_versions.xsd timthumbs.txt user-agents.txt wp_versions.xml wp_versions.xsd
plugin_vulns.json theme_vulns.json wp_vulns.json LICENSE wordpresses.json plugins.json themes.json LICENSE
) )
attr_reader :repo_directory attr_reader :repo_directory
@@ -22,13 +21,16 @@ class DbUpdater
def request_params def request_params
{ {
ssl_verifyhost: 2, ssl_verifyhost: 2,
ssl_verifypeer: true ssl_verifypeer: true,
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

View File

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

View File

@@ -42,11 +42,12 @@ class Vulnerability
# @return [ Vulnerability ] # @return [ Vulnerability ]
def self.load_from_json_item(json_item) def self.load_from_json_item(json_item)
references = {} references = {}
references['id'] = [json_item['id']]
%w(id url cve secunia osvdb metasploit exploitdb).each do |key| %w(url cve secunia osvdb metasploit exploitdb).each do |key|
if json_item[key] if json_item['references'][key]
json_item[key] = [json_item[key]] if json_item[key].class != Array json_item['references'][key] = [json_item['references'][key]] if json_item['references'][key].class != Array
references[key] = json_item[key] references[key] = json_item['references'][key]
end end
end end
@@ -54,7 +55,7 @@ class Vulnerability
json_item['title'], json_item['title'],
json_item['type'], json_item['type'],
references, references,
json_item['fixed_in'], json_item['fixed_in']
) )
end end

View File

@@ -2,22 +2,22 @@
class Vulnerability class Vulnerability
module Output module Output
# 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) 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
unless fixed_in.nil?
puts notice("Fixed in: #{fixed_in}") puts notice("Fixed in: #{fixed_in}") if fixed_in
end
end end
end end
end end

View File

@@ -22,7 +22,7 @@ class WpItem
# @return [ Array ] # @return [ Array ]
# Make it private ? # Make it private ?
def allowed_options def allowed_options
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :vulns_file] [:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :db_file]
end end
# @param [ URI ] target_base_uri # @param [ URI ] target_base_uri
@@ -30,7 +30,6 @@ class WpItem
# #
# @return [ WpItem ] # @return [ WpItem ]
def initialize(target_base_uri, options = {}) def initialize(target_base_uri, options = {})
options[:wp_content_dir] ||= 'wp-content' options[:wp_content_dir] ||= 'wp-content'
options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins' options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins'
@@ -38,6 +37,27 @@ class WpItem
forge_uri(target_base_uri) forge_uri(target_base_uri)
end end
def identifier
@identifier ||= name
end
# @return [ Hash ]
def db_data
@db_data ||= json(db_file)[identifier] || {}
end
def latest_version
db_data['latest_version']
end
def last_updated
db_data['last_ipdated']
end
def popular?
db_data['popular']
end
# @param [ Hash ] options # @param [ Hash ] options
# #
# @return [ void ] # @return [ void ]
@@ -80,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

View File

@@ -5,12 +5,17 @@ class WpItem
# @return [ Void ] # @return [ Void ]
def output(verbose = false) def output(verbose = false)
outdated = VersionCompare.lesser?(version, latest_version) if latest_version
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 " | Latest version: #{latest_version} #{'(up to date)' if version}" if latest_version && !outdated
puts " | Last updated: #{last_updated}" if last_updated
puts " | Location: #{url}" puts " | Location: #{url}"
#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("The version is out of date, the latest version is #{latest_version}") if latest_version && outdated
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?

View File

@@ -2,30 +2,23 @@
class WpItem class WpItem
module Vulnerable module Vulnerable
attr_accessor :vulns_file, :identifier attr_accessor :db_file, :identifier
# Get the vulnerabilities associated to the WpItem # Get the vulnerabilities associated to the WpItem
# Filters out already fixed vulnerabilities # Filters out already fixed vulnerabilities
# #
# @return [ Vulnerabilities ] # @return [ Vulnerabilities ]
def vulnerabilities def vulnerabilities
json = json(vulns_file) return @vulnerabilities if @vulnerabilities
vulnerabilities = Vulnerabilities.new
json.each do |item| @vulnerabilities = Vulnerabilities.new
asset = item[identifier]
next unless asset [*db_data['vulnerabilities']].each do |vulnerability|
asset['vulnerabilities'].each do |vulnerability|
vulnerability = Vulnerability.load_from_json_item(vulnerability) vulnerability = Vulnerability.load_from_json_item(vulnerability)
vulnerabilities << vulnerability if vulnerable_to?(vulnerability) @vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
end end
break # No need to iterate any further @vulnerabilities
end
vulnerabilities
end end
def vulnerable? def vulnerable?

View File

@@ -1,17 +1,16 @@
# encoding: UTF-8 # encoding: UTF-8
require 'wp_plugin/vulnerable'
class WpPlugin < WpItem class WpPlugin < WpItem
include WpPlugin::Vulnerable
# Sets the @uri # Sets the @uri
# #
# @param [ URI ] target_base_uri The URI of the wordpress blog # @param [ URI ] target_base_uri The URI of the wordpress blog
# #
# @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
@db_file ||= PLUGINS_FILE
end
end end

View File

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

View File

@@ -2,7 +2,6 @@
require 'wp_theme/findable' require 'wp_theme/findable'
require 'wp_theme/versionable' require 'wp_theme/versionable'
require 'wp_theme/vulnerable'
require 'wp_theme/info' require 'wp_theme/info'
require 'wp_theme/output' require 'wp_theme/output'
require 'wp_theme/childtheme' require 'wp_theme/childtheme'
@@ -10,7 +9,6 @@ require 'wp_theme/childtheme'
class WpTheme < WpItem class WpTheme < WpItem
extend WpTheme::Findable extend WpTheme::Findable
include WpTheme::Versionable include WpTheme::Versionable
include WpTheme::Vulnerable
include WpTheme::Info include WpTheme::Info
include WpTheme::Output include WpTheme::Output
include WpTheme::Childtheme include WpTheme::Childtheme
@@ -25,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
@@ -33,4 +31,7 @@ class WpTheme < WpItem
@uri.merge('style.css').to_s @uri.merge('style.css').to_s
end end
def db_file
@db_file ||= THEMES_FILE
end
end end

View File

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

View File

@@ -1,19 +0,0 @@
# encoding: UTF-8
class WpTheme < WpItem
module Vulnerable
# @return [ String ] The path to the file containing vulnerabilities
def vulns_file
unless @vulns_file
@vulns_file = THEMES_VULNS_FILE
end
@vulns_file
end
# @return [ String ]
def identifier
@name
end
end
end

View File

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

View File

@@ -39,7 +39,7 @@ 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)
return unless text =~ %r{/author/([^/\b]+)/?}i return unless text =~ %r{/author/([^/\b"']+)/?}i
Regexp.last_match[1].force_encoding('UTF-8') Regexp.last_match[1].force_encoding('UTF-8')
end end

View File

@@ -1,21 +1,27 @@
# encoding: UTF-8 # encoding: UTF-8
require 'wp_version/findable' require 'wp_version/findable'
require 'wp_version/vulnerable'
require 'wp_version/output' require 'wp_version/output'
class WpVersion < WpItem class WpVersion < WpItem
extend WpVersion::Findable extend WpVersion::Findable
include WpVersion::Vulnerable
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?
# @return [ Array ] # @return [ Array ]
def allowed_options; super << :number << :found_from end def allowed_options; super << :number << :found_from end
def identifier
@identifier ||= number
end
def db_file
@db_file ||= WORDPRESSES_FILE
end
# @param [ WpVersion ] other # @param [ WpVersion ] other
# #
# @return [ Boolean ] # @return [ Boolean ]
@@ -30,4 +36,13 @@ class WpVersion < WpItem
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

View File

@@ -31,7 +31,7 @@ class WpVersion < WpItem
# #
# @return [ String ] # @return [ String ]
def version_pattern def version_pattern
'([^\r\n"\']+\.[^\r\n"\']+)' '([^\r\n"\',]+\.[^\r\n"\',]+)'
end end
protected protected
@@ -68,7 +68,7 @@ class WpVersion < WpItem
def find_from_meta_generator(target_uri) def find_from_meta_generator(target_uri)
scan_url( scan_url(
target_uri, target_uri,
%r{name="generator" content="wordpress #{version_pattern}"}i %r{name="generator" content="wordpress #{version_pattern}.*"}i
) )
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

View File

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

View File

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

View File

@@ -11,8 +11,8 @@ class VersionCompare
# @return [ Boolean ] # @return [ Boolean ]
def self.lesser_or_equal?(version1, version2) def self.lesser_or_equal?(version1, version2)
# Prepend a '0' if the version starts with a '.' # Prepend a '0' if the version starts with a '.'
version1 = "0#{version1}" if version1 && version1[0,1] == '.' version1 = prepend_zero(version1)
version2 = "0#{version2}" if version2 && version2[0,1] == '.' version2 = prepend_zero(version2)
return true if (version1 == version2) return true if (version1 == version2)
# Both versions must be set # Both versions must be set
@@ -27,4 +27,36 @@ class VersionCompare
end end
return false return false
end end
# Compares two version strings. Returns true if version1 < version2
# and false otherwise
#
# @param [ String ] version1
# @param [ String ] version2
#
# @return [ Boolean ]
def self.lesser?(version1, version2)
# Prepend a '0' if the version starts with a '.'
version1 = prepend_zero(version1)
version2 = prepend_zero(version2)
return false if (version1 == version2)
# Both versions must be set
return false unless (version1 and version2)
return false if (version1.empty? or version2.empty?)
begin
return true if (Gem::Version.new(version1) < Gem::Version.new(version2))
rescue ArgumentError => e
# Example: ArgumentError: Malformed version number string a
return false if e.message =~ /Malformed version number string/
raise
end
return false
end
# @return [ String ]
def self.prepend_zero(version)
return nil if version.nil?
version[0,1] == '.' ? "0#{version}" : version
end
end end

View File

@@ -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,9 +30,10 @@ 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 'json' require 'yajl/json_gem'
require 'nokogiri' require 'nokogiri'
require 'terminal-table' require 'terminal-table'
require 'ruby-progressbar' require 'ruby-progressbar'

View File

@@ -28,6 +28,7 @@ class WebSite
if entries if entries
entries.flatten! entries.flatten!
entries.compact.sort! entries.compact.sort!
entries.uniq!
wordpress_path = @uri.path wordpress_path = @uri.path
RobotsTxt.known_dirs.each do |d| RobotsTxt.known_dirs.each do |d|
entries.delete(d) entries.delete(d)

View File

@@ -28,8 +28,13 @@ class WpTarget < WebSite
@wp_content_dir = options[:wp_content_dir] @wp_content_dir = options[:wp_content_dir]
@wp_plugins_dir = options[:wp_plugins_dir] @wp_plugins_dir = options[:wp_plugins_dir]
@multisite = nil @multisite = nil
@vhost = options[:vhost]
Browser.instance.referer = url Browser.instance.referer = url
if @vhost
Browser.instance.vhost = @vhost
end
end end
# check if the target website is # check if the target website is
@@ -44,11 +49,7 @@ class WpTarget < WebSite
fail "The target is responding with a 403, this might be due to a WAF or a plugin.\n" \ fail "The target is responding with a 403, this might be due to a WAF or a plugin.\n" \
'You should try to supply a valid user-agent via the --user-agent option or use the --random-agent option' if response.code == 403 'You should try to supply a valid user-agent via the --user-agent option or use the --random-agent option' if response.code == 403
if wp_content_dir dir = wp_content_dir ? wp_content_dir : 'wp-content'
dir = wp_content_dir
else
dir = 'wp-content'
end
if response.body =~ /["'][^"']*\/#{Regexp.escape(dir)}\/[^"']*["']/i if response.body =~ /["'][^"']*\/#{Regexp.escape(dir)}\/[^"']*["']/i
wordpress = true wordpress = true
@@ -134,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/
@@ -152,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

View File

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

View File

@@ -2,24 +2,21 @@
class WpTarget < WebSite class WpTarget < WebSite
module WpFullPathDisclosure module WpFullPathDisclosure
# Check for Full Path Disclosure (FPD) # Check for Full Path Disclosure (FPD)
# #
# @return [ Boolean ] # @return [ Boolean ]
def has_full_path_disclosure? def has_full_path_disclosure?
response = Browser.get(full_path_disclosure_url) Browser.get(full_path_disclosure_url).body[%r/Fatal error/i] ? true : false
response.body[%r{Fatal error}i] ? true : false
end end
def full_path_disclosure_data def full_path_disclosure_data
return nil unless has_full_path_disclosure? return nil unless has_full_path_disclosure?
Browser.get(full_path_disclosure_url).body[%r{<b>([^<]+\.php)</b>}, 1] Browser.get(full_path_disclosure_url).body[/Fatal error:.+? in (.+?) on/i, 1]
end 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
end end
end end
end end

View File

@@ -2,14 +2,13 @@
class WpTarget < WebSite class WpTarget < WebSite
module WpMustUsePlugins module WpMustUsePlugins
# Checks to see if the must use plugin folder exists # Checks to see if the must use plugin folder exists
# #
# @return [ Boolean ] # @return [ Boolean ]
def has_must_use_plugins? def has_must_use_plugins?
response = Browser.get(must_use_url) response = Browser.get(must_use_url)
if response && WpTarget.valid_response_codes.include?(response.code) if response && [200, 401, 403].include?(response.code)
hash = WebSite.page_hash(response) hash = WebSite.page_hash(response)
return true if hash != error_404_hash && hash != homepage_hash return true if hash != error_404_hash && hash != homepage_hash
end end
@@ -21,6 +20,5 @@ class WpTarget < WebSite
def must_use_url def must_use_url
@uri.merge("#{wp_content_dir}/mu-plugins/").to_s @uri.merge("#{wp_content_dir}/mu-plugins/").to_s
end end
end end
end end

View File

@@ -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.'
@@ -105,6 +105,7 @@ def help
puts '--request-timeout <request-timeout> Request Timeout.' puts '--request-timeout <request-timeout> Request Timeout.'
puts '--connect-timeout <connect-timeout> Connect Timeout.' puts '--connect-timeout <connect-timeout> Connect Timeout.'
puts '--max-threads <max-threads> Maximum Threads.' puts '--max-threads <max-threads> Maximum Threads.'
puts '--throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.'
puts '--help | -h This help screen.' puts '--help | -h This help screen.'
puts '--verbose | -v Verbose output.' puts '--verbose | -v Verbose output.'
puts '--version Output the current version and exit.' puts '--version Output the current version and exit.'
@@ -118,8 +119,14 @@ down = 0
@total_requests_done = 0 @total_requests_done = 0
Typhoeus.on_complete do |response| Typhoeus.on_complete do |response|
next if response.cached?
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 >= 30 fail 'The target seems to be down' if down >= 30
next unless Browser.instance.throttle > 0
sleep(Browser.instance.throttle)
end end

View File

@@ -1,7 +1,6 @@
# encoding: UTF-8 # encoding: UTF-8
class WpscanOptions class WpscanOptions
ACCESSOR_OPTIONS = [ ACCESSOR_OPTIONS = [
:batch, :batch,
:enumerate_plugins, :enumerate_plugins,
@@ -19,6 +18,7 @@ class WpscanOptions
:proxy_auth, :proxy_auth,
:threads, :threads,
:url, :url,
:vhost,
:wordlist, :wordlist,
:force, :force,
:update, :update,
@@ -42,7 +42,8 @@ class WpscanOptions
:request_timeout, :request_timeout,
:connect_timeout, :connect_timeout,
:max_threads, :max_threads,
:no_banner :no_banner,
:throttle
] ]
attr_accessor *ACCESSOR_OPTIONS attr_accessor *ACCESSOR_OPTIONS
@@ -61,6 +62,10 @@ class WpscanOptions
@url = URI.parse(add_http_protocol(url)).to_s @url = URI.parse(add_http_protocol(url)).to_s
end end
def vhost=(vhost)
@vhost = vhost
end
def threads=(threads) def threads=(threads)
@threads = threads.is_a?(Integer) ? threads : threads.to_i @threads = threads.is_a?(Integer) ? threads : threads.to_i
end end
@@ -246,6 +251,7 @@ class WpscanOptions
def self.get_opt_long def self.get_opt_long
GetoptLong.new( GetoptLong.new(
['--url', '-u', GetoptLong::REQUIRED_ARGUMENT], ['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
['--vhost',GetoptLong::OPTIONAL_ARGUMENT],
['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT], ['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT],
['--username', '-U', GetoptLong::REQUIRED_ARGUMENT], ['--username', '-U', GetoptLong::REQUIRED_ARGUMENT],
['--usernames', GetoptLong::REQUIRED_ARGUMENT], ['--usernames', GetoptLong::REQUIRED_ARGUMENT],
@@ -275,7 +281,8 @@ class WpscanOptions
['--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] ['--no-banner', GetoptLong::NO_ARGUMENT],
['--throttle', GetoptLong::REQUIRED_ARGUMENT]
) )
end end

View File

@@ -11,7 +11,7 @@ describe WpPlugins do
let(:expected) do let(:expected) do
{ {
request_params: { cache_ttl: 0, followlocation: true }, request_params: { cache_ttl: 0, followlocation: true },
vulns_file: PLUGINS_VULNS_FILE, vulns_file: PLUGINS_FILE,
targets_items_from_file: [ WpPlugin.new(uri, name: 'plugin1'), targets_items_from_file: [ WpPlugin.new(uri, name: 'plugin1'),
WpPlugin.new(uri, name:'plugin-2'), WpPlugin.new(uri, name:'plugin-2'),
WpPlugin.new(uri, name: 'mr-smith')], WpPlugin.new(uri, name: 'mr-smith')],

View File

@@ -13,7 +13,7 @@ describe WpThemes do
let(:expected) do let(:expected) do
{ {
request_params: { cache_ttl: 0, followlocation: true }, request_params: { cache_ttl: 0, followlocation: true },
vulns_file: THEMES_VULNS_FILE, vulns_file: THEMES_FILE,
targets_items_from_file: [ WpTheme.new(uri, name: '3colours'), targets_items_from_file: [ WpTheme.new(uri, name: '3colours'),
WpTheme.new(uri, name:'42k'), WpTheme.new(uri, name:'42k'),
WpTheme.new(uri, name: 'a-ri')], WpTheme.new(uri, name: 'a-ri')],

View File

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

View File

@@ -11,11 +11,11 @@ describe WpItem do
end end
it_behaves_like 'WpItem::Versionable' it_behaves_like 'WpItem::Versionable'
it_behaves_like 'WpItem::Vulnerable' do it_behaves_like 'WpItem::Vulnerable' do
let(:vulns_file) { MODELS_FIXTURES + '/wp_item/vulnerable/items_vulns.json' } let(:db_file) { MODELS_FIXTURES + '/wp_item/vulnerable/items_vulns.json' }
let(:identifier) { 'neo' } let(:identifier) { 'neo' }
let(:expected_refs) { { let(:expected_refs) { {
'id' => [2993], 'id' => [2993],
'url' => ['Ref 1,Ref 2'], 'url' => ['Ref 1', 'Ref 2'],
'cve' => ['2011-001'], 'cve' => ['2011-001'],
'secunia' => ['secunia'], 'secunia' => ['secunia'],
'osvdb' => ['osvdb'], 'osvdb' => ['osvdb'],
@@ -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

View File

@@ -6,10 +6,10 @@ describe WpPlugin do
it_behaves_like 'WpPlugin::Vulnerable' it_behaves_like 'WpPlugin::Vulnerable'
it_behaves_like 'WpItem::Vulnerable' do it_behaves_like 'WpItem::Vulnerable' do
let(:options) { { name: 'white-rabbit' } } let(:options) { { name: 'white-rabbit' } }
let(:vulns_file) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins_vulns.json' } let(:db_file) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins.json' }
let(:expected_refs) { { let(:expected_refs) { {
'id' => [2993], 'id' => [2993],
'url' => ['Ref 1,Ref 2'], 'url' => ['Ref 1', 'Ref 2'],
'cve' => ['2011-001'], 'cve' => ['2011-001'],
'secunia' => ['secunia'], 'secunia' => ['secunia'],
'osvdb' => ['osvdb'], 'osvdb' => ['osvdb'],

View File

@@ -7,10 +7,10 @@ describe WpTheme do
it_behaves_like 'WpTheme::Vulnerable' it_behaves_like 'WpTheme::Vulnerable'
it_behaves_like 'WpItem::Vulnerable' do it_behaves_like 'WpItem::Vulnerable' do
let(:options) { { name: 'the-oracle' } } let(:options) { { name: 'the-oracle' } }
let(:vulns_file) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' } let(:db_file) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' }
let(:expected_refs) { { let(:expected_refs) { {
'id' => [2993], 'id' => [2993],
'url' => ['Ref 1,Ref 2'], 'url' => ['Ref 1', 'Ref 2'],
'cve' => ['2011-001'], 'cve' => ['2011-001'],
'secunia' => ['secunia'], 'secunia' => ['secunia'],
'osvdb' => ['osvdb'], 'osvdb' => ['osvdb'],

View File

@@ -65,6 +65,11 @@ describe 'WpVersion::Findable' do
@fixture = '/3.5_minified.html' @fixture = '/3.5_minified.html'
@expected = '3.5' @expected = '3.5'
end end
it 'returns 3.5.1' do
@fixture = '/3.5.1_mobile.html'
@expected = '3.5.1'
end
end end
end end

View File

@@ -4,20 +4,6 @@ require 'spec_helper'
describe WpVersion do describe WpVersion do
it_behaves_like 'WpVersion::Vulnerable' it_behaves_like 'WpVersion::Vulnerable'
it_behaves_like 'WpItem::Vulnerable' do
let(:options) { { number: '3.2' } }
let(:vulns_file) { MODELS_FIXTURES + '/wp_version/vulnerable/versions_vulns.json' }
let(:expected_refs) { {
'id' => [2993],
'url' => ['Ref 1,Ref 2'],
'cve' => ['2011-001'],
'secunia' => ['secunia'],
'osvdb' => ['osvdb'],
'metasploit' => ['exploit/ex1'],
'exploitdb' => ['exploitdb']
} }
let(:expected_vulns) { Vulnerabilities.new << Vulnerability.new('Here I Am', 'SQLI', expected_refs) }
end
subject(:wp_version) { WpVersion.new(uri, options) } subject(:wp_version) { WpVersion.new(uri, options) }
let(:uri) { URI.parse('http://example.com/') } let(:uri) { URI.parse('http://example.com/') }

View File

@@ -121,4 +121,122 @@ describe 'VersionCompare' do
end end
end end
describe '::lesser?' do
context 'version checked is newer' do
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_truthy }
it 'returns true' do
@version1 = '1.0'
@version2 = '2.0'
end
it 'returns true' do
@version1 = '1.0'
@version2 = '1.1'
end
it 'returns true' do
@version1 = '1.0a'
@version2 = '1.0b'
end
it 'returns true' do
@version1 = '1.0'
@version2 = '5000000'
end
it 'returns true' do
@version1 = '0'
@version2 = '1'
end
it 'returns true' do
@version1 = '0.4.2b'
@version2 = '2.3.3'
end
it 'returns true' do
@version1 = '.47'
@version2 = '.50.3'
end
end
context 'version checked is older' do
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
it 'returns false' do
@version1 = '1'
@version2 = '0'
end
it 'returns false' do
@version1 = '1.0'
@version2 = '0.5'
end
it 'returns false' do
@version1 = '500000'
@version2 = '1'
end
it 'returns false' do
@version1 = '1.6.3.7.3.4'
@version2 = '1.2.4.567.679.8.e'
end
it 'returns false' do
@version1 = '.47'
@version2 = '.46.3'
end
end
context 'version checked is the same' do
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
it 'returns true' do
@version1 = '1'
@version2 = '1'
end
it 'returns true' do
@version1 = 'a'
@version2 = 'a'
end
end
context 'version number causes Gem::Version new Exception' do
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
it 'returns false' do
@version1 = 'a'
@version2 = 'b'
end
end
context 'one version number is not set' do
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
it 'returns false (version2 nil)' do
@version1 = '1'
@version2 = nil
end
it 'returns false (version1 nil)' do
@version1 = nil
@version2 = '1'
end
it 'returns false (version2 empty)' do
@version1 = '1'
@version2 = ''
end
it 'returns false (version1 empty)' do
@version1 = ''
@version2 = '1'
end
end
end
end end

View File

@@ -1,20 +1,23 @@
[
{ {
"mr-smith": { "mr-smith": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2989, "id":2989,
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1", "title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com", "references": {
"url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:43:41.000Z" "updated_at":"2014-07-28T12:43:41.000Z"
}, },
{ {
"id":2990, "id":2990,
"title":"Potential Authentication Cookie Forgery", "title":"Potential Authentication Cookie Forgery",
"references":"https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be", "references": {
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
"osvdb":"105620", "osvdb":"105620",
"cve":"2014-0166", "cve":"2014-0166"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z", "updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "fixed_in":"3.8.2"
@@ -22,9 +25,11 @@
{ {
"id":2991, "id":2991,
"title":"Privilege escalation: contributors publishing posts", "title":"Privilege escalation: contributors publishing posts",
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165", "references": {
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
"osvdb":"105630", "osvdb":"105630",
"cve":"2014-0165", "cve":"2014-0165"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z", "updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "fixed_in":"3.8.2"
@@ -32,27 +37,28 @@
{ {
"id":2992, "id":2992,
"title":"Plupload Unspecified XSS", "title":"Plupload Unspecified XSS",
"references": {
"osvdb":"105622", "osvdb":"105622",
"secunia":"57769", "secunia":"57769"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z", "updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "fixed_in":"3.8.2"
} }
] ]
}
}, },
{
"neo": { "neo": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2993, "id":2993,
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure", "title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
"references":"http://seclists.org/fulldisclosure/2013/Dec/135", "references": {
"osvdb":"101101", "url": "http://seclists.org/fulldisclosure/2013/Dec/135",
"osvdb":"101101"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z" "updated_at":"2014-07-28T12:10:07.000Z"
} }
] ]
} }
} }
]

View File

@@ -1,18 +1,21 @@
[
{ {
"mr-smith": { "mr-smith": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2989, "id":2989,
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1", "title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com", "references": {
"url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:43:41.000Z" "updated_at":"2014-07-28T12:43:41.000Z"
}, },
{ {
"id":2990, "id":2990,
"title":"Potential Authentication Cookie Forgery", "title":"Potential Authentication Cookie Forgery",
"references":"https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be", "references": {
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be"
},
"osvdb":"105620", "osvdb":"105620",
"cve":"2014-0166", "cve":"2014-0166",
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
@@ -22,9 +25,11 @@
{ {
"id":2991, "id":2991,
"title":"Privilege escalation: contributors publishing posts", "title":"Privilege escalation: contributors publishing posts",
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165", "references": {
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
"osvdb":"105630", "osvdb":"105630",
"cve":"2014-0165", "cve":"2014-0165"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z", "updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "fixed_in":"3.8.2"
@@ -32,27 +37,28 @@
{ {
"id":2992, "id":2992,
"title":"Plupload Unspecified XSS", "title":"Plupload Unspecified XSS",
"references": {
"osvdb":"105622", "osvdb":"105622",
"secunia":"57769", "secunia":"57769"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z", "updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "fixed_in":"3.8.2"
} }
] ]
}
}, },
{
"neo": { "neo": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2993, "id":2993,
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure", "title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
"references":"http://seclists.org/fulldisclosure/2013/Dec/135", "references": {
"osvdb":"101101", "url": "http://seclists.org/fulldisclosure/2013/Dec/135",
"osvdb":"101101"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z" "updated_at":"2014-07-28T12:10:07.000Z"
} }
] ]
} }
} }
]

View File

@@ -1,20 +1,23 @@
[
{ {
"shopperpress": { "shopperpress": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2989, "id":2989,
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1", "title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com", "references": {
"url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:43:41.000Z" "updated_at":"2014-07-28T12:43:41.000Z"
}, },
{ {
"id":2990, "id":2990,
"title":"Potential Authentication Cookie Forgery", "title":"Potential Authentication Cookie Forgery",
"references":"https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be", "references": {
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
"osvdb":"105620", "osvdb":"105620",
"cve":"2014-0166", "cve":"2014-0166"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z", "updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "fixed_in":"3.8.2"
@@ -22,9 +25,11 @@
{ {
"id":2991, "id":2991,
"title":"Privilege escalation: contributors publishing posts", "title":"Privilege escalation: contributors publishing posts",
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165", "references": {
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
"osvdb":"105630", "osvdb":"105630",
"cve":"2014-0165", "cve":"2014-0165"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z", "updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "fixed_in":"3.8.2"
@@ -32,27 +37,29 @@
{ {
"id":2992, "id":2992,
"title":"Plupload Unspecified XSS", "title":"Plupload Unspecified XSS",
"references": {
"osvdb":"105622", "osvdb":"105622",
"secunia":"57769", "secunia":"57769"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z", "updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "fixed_in":"3.8.2"
} }
] ]
}
}, },
{
"webfolio": { "webfolio": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2993, "id":2993,
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure", "title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
"references":"http://seclists.org/fulldisclosure/2013/Dec/135", "references": {
"osvdb":"101101", "url": "http://seclists.org/fulldisclosure/2013/Dec/135",
"osvdb":"101101"
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z" "updated_at":"2014-07-28T12:10:07.000Z"
} }
] ]
} }
} }
]

View File

@@ -1,12 +1,14 @@
{ {
"id": "3911", "id": "3911",
"title": "Vuln Title", "title": "Vuln Title",
"references":{
"url": "Ref 1,Ref 2", "url": "Ref 1,Ref 2",
"secunia": "secunia", "secunia": "secunia",
"osvdb": "osvdb", "osvdb": "osvdb",
"cve": "2011-001", "cve": "2011-001",
"metasploit": "exploit/ex1", "metasploit": "exploit/ex1",
"exploitdb": "exploitdb", "exploitdb": "exploitdb"
},
"created_at": "2014-07-28T12:10:45.000Z", "created_at": "2014-07-28T12:10:45.000Z",
"updated_at": "2014-07-28T12:10:45.000Z", "updated_at": "2014-07-28T12:10:45.000Z",
"type": "CSRF", "type": "CSRF",

View File

@@ -1,29 +1,30 @@
[
{ {
"not-this-one": { "not-this-one": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2989, "id":2989,
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1", "title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
"url":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com", "references": {
"url": ["https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/" ,"http://www.example.com"]
},
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:43:41.000Z" "updated_at":"2014-07-28T12:43:41.000Z"
} }
] ]
}
}, },
{
"neo": { "neo": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2993, "id":2993,
"title":"I'm the one", "title":"I'm the one",
"url":"Ref 1,Ref 2", "references": {
"osvdb":"osvdb", "url": ["Ref 1", "Ref 2"],
"cve":"2011-001", "osvdb": ["osvdb"],
"secunia":"secunia", "cve": ["2011-001"],
"metasploit":"exploit/ex1", "secunia": ["secunia"],
"exploitdb":"exploitdb", "metasploit": ["exploit/ex1"],
"exploitdb": ["exploitdb"]
},
"type":"XSS", "type":"XSS",
"fixed_in":"", "fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
@@ -32,4 +33,3 @@
] ]
} }
} }
]

View File

@@ -0,0 +1,58 @@
{
"mr-smith": {
"vulnerabilities":[
{
"id":2993,
"title":"I should not appear in the results",
"references": {
"url": ["Ref 1","Ref 2"],
"osvdb": ["osvdb"],
"cve": ["2011-001"],
"secunia": ["secunia"],
"metasploit": ["exploit/ex1"],
"exploitdb": ["exploitdb"]
},
"type":"XSS",
"fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z"
},
{
"id":2989,
"title":"Neither do I",
"references": {
"url": ["Ref 1" ,"Ref 2"],
"osvdb": ["osvdb"],
"cve": ["2011-001"],
"secunia": ["secunia"],
"metasploit": ["exploit/ex1"],
"exploitdb": ["exploitdb"]
},
"type":"XSS",
"fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z"
}
]
},
"white-rabbit": {
"vulnerabilities": [
{
"id":2993,
"title":"Follow me!",
"references": {
"url": ["Ref 1", "Ref 2"],
"osvdb": ["osvdb"],
"cve": ["2011-001"],
"secunia": ["secunia"],
"metasploit": ["exploit/ex1"],
"exploitdb": ["exploitdb"]
},
"type":"REDIRECT",
"fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z"
}
]
}
}

View File

@@ -1,56 +0,0 @@
[
{
"mr-smith":{
"vulnerabilities":[
{
"id":2989,
"title":"I should not appear in the results",
"url":"Ref 1,Ref 2",
"osvdb":"osvdb",
"cve":"2011-001",
"secunia":"secunia",
"metasploit":"exploit/ex1",
"exploitdb":"exploitdb",
"type":"XSS",
"fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z"
},
{
"id":2989,
"title":"Neither do I",
"url":"Ref 1,Ref 2",
"osvdb":"osvdb",
"cve":"2011-001",
"secunia":"secunia",
"metasploit":"exploit/ex1",
"exploitdb":"exploitdb",
"type":"XSS",
"fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z"
}
]
}
},
{
"white-rabbit":{
"vulnerabilities":[
{
"id":2993,
"title":"Follow me!",
"url":"Ref 1,Ref 2",
"osvdb":"osvdb",
"cve":"2011-001",
"secunia":"secunia",
"metasploit":"exploit/ex1",
"exploitdb":"exploitdb",
"type":"REDIRECT",
"fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z"
}
]
}
}
]

View File

@@ -1,16 +1,17 @@
[
{ {
"mr-smith": { "mr-smith": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2989, "id":2989,
"title":"I should not appear in the results", "title":"I should not appear in the results",
"url":"Ref 1,Ref 2", "references": {
"osvdb":"osvdb", "url": ["Ref 1", "Ref 2"],
"cve":"2011-001", "osvdb": ["osvdb"],
"secunia":"secunia", "cve": ["2011-001"],
"metasploit":"exploit/ex1", "secunia": ["secunia"],
"exploitdb":"exploitdb", "metasploit": ["exploit/ex1"],
"exploitdb": ["exploitdb"]
},
"type":"XSS", "type":"XSS",
"fixed_in":"", "fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
@@ -19,32 +20,34 @@
{ {
"id":2989, "id":2989,
"title":"Neither do I", "title":"Neither do I",
"url":"Ref 1,Ref 2", "references": {
"osvdb":"osvdb", "url": ["Ref 1", "Ref 2"],
"cve":"2011-001", "osvdb": ["osvdb"],
"secunia":"secunia", "cve": ["2011-001"],
"metasploit":"exploit/ex1", "secunia": ["secunia"],
"exploitdb":"exploitdb", "metasploit": ["exploit/ex1"],
"exploitdb": ["exploitdb"]
},
"type":"XSS", "type":"XSS",
"fixed_in":"", "fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z" "updated_at":"2014-07-28T12:10:07.000Z"
} }
] ]
}
}, },
{
"the-oracle": { "the-oracle": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2993, "id":2993,
"title":"I see you", "title":"I see you",
"url":"Ref 1,Ref 2", "references": {
"osvdb":"osvdb", "url": ["Ref 1", "Ref 2"],
"cve":"2011-001", "osvdb": ["osvdb"],
"secunia":"secunia", "cve": ["2011-001"],
"metasploit":"exploit/ex1", "secunia": ["secunia"],
"exploitdb":"exploitdb", "metasploit": ["exploit/ex1"],
"exploitdb": ["exploitdb"]
},
"type":"FPD", "type":"FPD",
"fixed_in":"", "fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
@@ -53,4 +56,4 @@
] ]
} }
} }
]

View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en-US" prefix="og: http://ogp.me/ns#">
<head>
<meta charset="UTF-8" />
<title>Test</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="http://example.com/wp-content/themes/scrollider child/style.css" media="screen" />
<link rel="stylesheet" type="text/css" media="print"
href="http://example.com/wp-content/themes/scrollider child/print.css" />
<link rel="pingback" href="http://example.com/xmlrpc.php" />
<!-- This site is optimized with the Yoast WordPress SEO plugin v1.6.1 - https://yoast.com/wordpress/plugins/seo/ -->
<link rel="canonical" href="http://example.com" />
<link rel="next" href="http://example.com/page/2/" />
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Test" />
<meta property="og:url" content="http://example.com" />
<meta property="og:site_name" content="Test" />
<script type="application/ld+json">{ "@context": "http://schema.org", "@type": "WebSite", "url": "http://example.com/", "potentialAction": { "@type": "SearchAction", "target": "http://example.com/?s={search_term}", "query-input": "required name=search_term" } }</script>
<!-- / Yoast WordPress SEO plugin. -->
<link rel="alternate" type="application/rss+xml" title="Test" href="http://www.example.com/feed" />
<link rel="alternate" type="application/rss+xml" title="Test" href="http://example.com/comments/feed/" />
<link rel='stylesheet' id='colorbox-theme3-css' href='http://example.com/wp-content/plugins/ewsel-lightbox-for-galleries/colorbox/theme3/colorbox.css?ver=1.3.14' type='text/css' media='screen' />
<link rel='stylesheet' id='woo-layout-css' href='http://example.com/wp-content/themes/scrollider/css/layout.css?ver=3.5.1' type='text/css' media='all' />
<link rel='stylesheet' id='woocommerce-css' href='http://example.com/wp-content/themes/scrollider/css/woocommerce.css?ver=3.5.1' type='text/css' media='all' />
<link rel='stylesheet' id='contact-form-7-css' href='http://example.com/wp-content/plugins/contact-form-7/includes/css/styles.css?ver=3.4.2' type='text/css' media='all' />
<link rel='stylesheet' id='videogallery_css-css' href='http://example.com/wp-content/plugins/contus-video-gallery/css/style.min.css?ver=3.5.1' type='text/css' media='all' />
<link rel='stylesheet' id='SFSImainCss-css' href='http://example.com/wp-content/plugins/ultimate-social-media-icons/css/sfsi-style.css?ver=3.5.1' type='text/css' media='all' />
<link rel='stylesheet' id='SFSIJqueryCSS-css' href='http://example.com/wp-content/plugins/ultimate-social-media-icons/css/jquery-ui-1.10.4/jquery-ui-min.css?ver=3.5.1' type='text/css' media='all' />
<link rel='stylesheet' id='prettyPhoto-css' href='http://example.com/wp-content/themes/scrollider/includes/css/prettyPhoto.css?ver=3.5.1' type='text/css' media='all' />
<script type='text/javascript' src='http://example.com/wp-includes/js/jquery/jquery.js?ver=1.8.3'></script>
<script type='text/javascript' src='http://example.com/wp-content/plugins/ewsel-lightbox-for-galleries/colorbox/jquery.colorbox-min.js?ver=1.3.14'></script>
<script type='text/javascript' src='http://example.com/wp-content/plugins/contus-video-gallery/js/script.min.js?ver=3.5.1'></script>
<script type='text/javascript'>
/* <![CDATA[ */
var ajax_object = {"ajax_url":"http:\/\/example.com\/wp-admin\/admin-ajax.php"};
/* ]]> */
</script>
<script type='text/javascript' src='http://example.com/wp-content/plugins/ultimate-social-media-icons/js/custom.js?ver=3.5.1'></script>
<script type='text/javascript' src='http://example.com/wp-content/themes/scrollider/includes/js/third-party.js?ver=3.5.1'></script>
<script type='text/javascript' src='http://example.com/wp-content/themes/scrollider/includes/js/general.js?ver=3.5.1'></script>
<script type='text/javascript'>
/* <![CDATA[ */
var woo_masonry_data = {"numberOfColumns":"3"};
/* ]]> */
</script>
<script type='text/javascript' src='http://example.com/wp-content/themes/scrollider/includes/js/jquery.masonry.min.js?ver=3.5.1'></script>
<script type='text/javascript' src='http://example.com/wp-content/themes/scrollider/includes/js/jquery.flexslider-min.js?ver=3.5.1'></script>
<script type='text/javascript'>
/* <![CDATA[ */
var woo_localized_data = {"animation":"fade","controlsContainer":".controls-container","smoothHeight":"true","useCSS":"true","directionNav":"true","controlNav":"true","manualControls":".manual ol li","slideshow":"true","pauseOnHover":"false","slideshowSpeed":"3000","animationDuration":"0","touch":"false"};
/* ]]> */
</script>
<script type='text/javascript' src='http://example.com/wp-content/themes/scrollider/includes/js/featured-slider.js?ver=1.2.5'></script>
<script type='text/javascript' src='http://example.com/wp-content/plugins/vslider/js/vslider.js?ver=3.5.1'></script>
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://example.com/xmlrpc.php?rsd" />
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://example.com/wp-includes/wlwmanifest.xml" />
<meta name="generator" content="WordPress 3.5.1, fitted with the WordPress Mobile Pack 1.2.5" />
<meta name="viewport" content="width=device-width, initial-scale=1"><meta property="og:image" content="http://example.com/wp-content/uploads/2015/07/test.jpg"><meta property="og:image:type" content=""><meta property="og:image:width" content="1000"><meta property="og:image:height" content="752">
<!-- Theme version -->
<meta name="generator" content="Scrollider Child 1.0.0" />
<meta name="generator" content="Scrollider 1.2.13" />
<meta name="generator" content="WooFramework 5.5.5" />

View File

@@ -1,36 +1,37 @@
[
{ {
"3.5": { "3.5": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2989, "id":2989,
"title":"I should not appear in the results", "title":"I should not appear in the results",
"url":"Ref 1,Ref 2", "references": {
"osvdb":"osvdb", "url": ["Ref 1", "Ref 2"],
"cve":"2011-001", "osvdb": ["osvdb"],
"secunia":"secunia", "cve": ["2011-001"],
"metasploit":"exploit/ex1", "secunia": ["secunia"],
"exploitdb":"exploitdb", "metasploit": ["exploit/ex1"],
"exploitdb": ["exploitdb"]
},
"type":"XSS", "type":"XSS",
"fixed_in":"", "fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z" "updated_at":"2014-07-28T12:10:07.000Z"
} }
] ]
}
}, },
{
"3.2": { "3.2": {
"vulnerabilities":[ "vulnerabilities":[
{ {
"id":2993, "id":2993,
"title":"Here I Am", "title":"Here I Am",
"url":"Ref 1,Ref 2", "references": {
"osvdb":"osvdb", "url": ["Ref 1", "Ref 2"],
"cve":"2011-001", "osvdb": ["osvdb"],
"secunia":"secunia", "cve": ["2011-001"],
"metasploit":"exploit/ex1", "secunia": ["secunia"],
"exploitdb":"exploitdb", "metasploit": ["exploit/ex1"],
"exploitdb": ["exploitdb"]
},
"type":"SQLI", "type":"SQLI",
"fixed_in":"", "fixed_in":"",
"created_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
@@ -39,4 +40,3 @@
] ]
} }
} }
]

View File

@@ -0,0 +1,17 @@
User-agent: *
Disallow: /wp-admin/
Disallow: /wp-admin/
Disallow: /wp-admin/
Disallow: /wp-admin/
Disallow: /wp-includes/
Disallow: /wordpress/admin/
Disallow: /wordpress/wp-admin/
Disallow: /wordpress/secret/
Disallow: /wordpress/secret/
Disallow: /wordpress/
Disallow: /wordpress/secret/
Disallow: /Wordpress/wp-admin/
Disallow: /wp-admin/tralling-space/
Allow: /asdf/
Sitemap: http://10.0.0.0/sitemap.xml.gz

View File

@@ -0,0 +1,9 @@
User-agent: *
Disallow: /wp-admin/
Disallow: /wp-admin/
Disallow: /wp-admin/
Disallow: /wp-admin/
Disallow: /wp-admin/
Disallow: /wp-admin/
Sitemap: http://10.0.0.0/sitemap.xml.gz

View File

@@ -61,6 +61,24 @@ shared_examples 'WebSite::RobotsTxt' do
http://example.localhost/asdf/ http://example.localhost/asdf/
) )
end end
it 'removes duplicate entries from robots.txt test 1' do
@fixture = fixtures_dir + '/robots_txt/robots_duplicate_1.txt'
@expected = %w(
http://example.localhost/wordpress/
http://example.localhost/wordpress/admin/
http://example.localhost/wordpress/wp-admin/
http://example.localhost/wordpress/secret/
http://example.localhost/Wordpress/wp-admin/
http://example.localhost/wp-admin/tralling-space/
http://example.localhost/asdf/
)
end
it 'removes duplicate entries from robots.txt test 2' do
@fixture = fixtures_dir + '/robots_txt/robots_duplicate_2.txt'
@expected = nil
end
end end
context 'installed in sub directory' do context 'installed in sub directory' do

View File

@@ -3,8 +3,8 @@
shared_examples 'WpItem::Vulnerable' do shared_examples 'WpItem::Vulnerable' do
# 2 variables have to be set in the described class or subject: # 2 variables have to be set in the described class or subject:
# let(:vulns_file) { } # let(:db_file) { }
# let(:expected_vulns) { } The expected Vulnerabilities when using vulns_file and vulns_xpath # let(:expected_vulns) { } The expected Vulnerabilities when using db_file and vulns_xpath
# #
# 1 variable is optional, used if supplied, otherwise subject.vulns_xpath is used # 1 variable is optional, used if supplied, otherwise subject.vulns_xpath is used
# let(:vulns_xpath) { } # let(:vulns_xpath) { }
@@ -18,7 +18,7 @@ shared_examples 'WpItem::Vulnerable' do
end end
after do after do
subject.vulns_file = @vulns_file subject.db_file = @db_file
subject.identifier = identifier if defined?(identifier) subject.identifier = identifier if defined?(identifier)
result = subject.vulnerabilities result = subject.vulnerabilities
@@ -26,15 +26,15 @@ shared_examples 'WpItem::Vulnerable' do
expect(result).to eq @expected expect(result).to eq @expected
end end
context 'when the vulns_file is empty' do context 'when the db_file is empty' do
it 'returns an empty Vulnerabilities' do it 'returns an empty Vulnerabilities' do
@vulns_file = empty_file @db_file = empty_file
@expected = Vulnerabilities.new @expected = Vulnerabilities.new
end end
end end
it 'returns the expected vulnerabilities' do it 'returns the expected vulnerabilities' do
@vulns_file = vulns_file @db_file = db_file
@expected = expected_vulns @expected = expected_vulns
end end
end end

View File

@@ -39,68 +39,8 @@ shared_examples 'WpItems::Detectable' do
end end
end end
describe '::targets_items_from_file' do
after do
results = subject.send(:targets_items_from_file, file, wp_target, item_class, vulns_file)
expect(results.map { |i| i.name }).to eq @expected.map { |i| i.name }
unless results.empty?
results.each do |item|
expect(item).to be_a item_class
end
end
end
# should raise error.
# context 'when an empty file' do
# let(:file) { empty_file }
# it 'returns an empty Array' do
# @expected = []
# end
# end
context 'when a file' do
let(:file) { targets_items_file }
it 'returns the expected Array of WpItem' do
@expected = expected[:targets_items_from_file]
end
end
end
describe '::vulnerable_targets_items' do
after do
results = subject.send(:vulnerable_targets_items, wp_target, item_class, vulns_file)
expect(results.map { |i| i.name }).to eq @expected.map { |i| i.name }
unless results.empty?
results.each do |item|
expect(item).to be_a item_class
end
end
end
# should raise error.
# context 'when an empty file' do
# let(:file) { empty_file }
# it 'returns an empty Array' do
# @expected = []
# end
# end
context 'when a file' do
it 'returns the expected Array of WpItem' do
@expected = expected[:vulnerable_targets_items]
end
end
end
describe '::targets_items' do describe '::targets_items' do
let(:options) { {} } let(:options) { { type: :all } }
after do after do
if @expected if @expected
@@ -110,29 +50,13 @@ shared_examples 'WpItems::Detectable' do
end end
end end
context 'when :only_vulnerable' do context 'when :type = :vulnerable' do
let(:options) { { only_vulnerable: true } } let(:options) { { type: :vulnerable } }
it 'returns the expected Array of WpItem' do it 'returns the expected Array of WpItem' do
@expected = expected[:vulnerable_targets_items] @expected = expected[:vulnerable_targets_items]
end end
end end
context 'when not :only_vulnerable' do
context 'when no :file' do
it 'raises an error' do
expect { subject.send(:targets_items, wp_target, options) }.to raise_error('A file must be supplied')
end
end
context 'when :file' do
let(:options) { { file: targets_items_file } }
it 'returns the expected Array of WpItem' do
@expected = (expected[:targets_items_from_file] + expected[:vulnerable_targets_items]).uniq {|t| t.name }
end
end
end
end end
describe '::passive_detection' do describe '::passive_detection' do
@@ -176,8 +100,8 @@ shared_examples 'WpItems::Detectable' do
expect(result.sort.map { |i| i.name }).to eq @expected.sort.map { |i| i.name } expect(result.sort.map { |i| i.name }).to eq @expected.sort.map { |i| i.name }
end end
context 'when :only_vulnerable' do context 'when :type = :vulnerable' do
let(:options) { { only_vulnerable: true } } let(:options) { { type: :vulnerable } }
let(:targets) { expected[:vulnerable_targets_items] } let(:targets) { expected[:vulnerable_targets_items] }
it 'only checks and return vulnerable targets' do it 'only checks and return vulnerable targets' do
@@ -207,7 +131,7 @@ shared_examples 'WpItems::Detectable' do
end end
end end
context 'when no :only_vulnerable' do context 'when no :type = :vulnerable' do
let(:targets) { (expected[:vulnerable_targets_items] + expected[:targets_items_from_file]).uniq { |t| t.name } } let(:targets) { (expected[:vulnerable_targets_items] + expected[:targets_items_from_file]).uniq { |t| t.name } }
it 'checks all targets, and merge the results with passive_detection' do it 'checks all targets, and merge the results with passive_detection' do

View File

@@ -2,25 +2,25 @@
shared_examples 'WpPlugin::Vulnerable' do shared_examples 'WpPlugin::Vulnerable' do
describe '#vulns_file' do describe '#db_file' do
after { expect(subject.vulns_file).to eq @expected } after { expect(subject.db_file).to eq @expected }
context 'when :vulns_file is no set' do context 'when :db_file is no set' do
it 'returns the default one' do it 'returns the default one' do
@expected = PLUGINS_VULNS_FILE @expected = PLUGINS_FILE
end end
end end
context 'when the :vulns_file is already set' do context 'when the :db_file is already set' do
it 'returns it' do it 'returns it' do
@expected = 'test.json' @expected = 'test.json'
subject.vulns_file = @expected subject.db_file = @expected
end end
end end
end end
describe '#identifier' do describe '#identifier' do
its(:identifier) { is_expected.to eq 'plugin-name' } its(:identifier) { should eq 'plugin-name' }
end end
end end

View File

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

View File

@@ -2,25 +2,25 @@
shared_examples 'WpTheme::Vulnerable' do shared_examples 'WpTheme::Vulnerable' do
describe '#vulns_file' do describe '#db_file' do
after { expect(subject.vulns_file).to eq @expected } after { expect(subject.db_file).to eq @expected }
context 'when :vulns_file is not set' do context 'when :db_file is not set' do
it 'returns the default one' do it 'returns the default one' do
@expected = THEMES_VULNS_FILE @expected = THEMES_FILE
end end
end end
context 'when the :vulns_file is already set' do context 'when the :db_file is already set' do
it 'returns it' do it 'returns it' do
@expected = 'test.json' @expected = 'test.json'
subject.vulns_file = @expected subject.db_file = @expected
end end
end end
end end
describe '#identifier' do describe '#identifier' do
its(:identifier) { is_expected.to eq 'theme-name' } its(:identifier) { should eq 'theme-name' }
end end
end end

View File

@@ -29,6 +29,13 @@ shared_examples 'WpUser::Existable' do
@expected = nil @expected = nil
end end
end end
context 'when no author given' do
it 'returns nil' do
@text = '<a href="http://wp.lab/author/" class="btn btn-default">See Posts</a>'
@expected = nil
end
end
end end
describe '::login_from_body' do describe '::login_from_body' do

View File

@@ -2,25 +2,25 @@
shared_examples 'WpVersion::Vulnerable' do shared_examples 'WpVersion::Vulnerable' do
describe '#vulns_file' do describe '#db_file' do
after { expect(subject.vulns_file).to eq @expected } after { expect(subject.db_file).to eq @expected }
context 'when :vulns_file is no set' do context 'when :db_file is no set' do
it 'returns the default one' do it 'returns the default one' do
@expected = WP_VULNS_FILE @expected = WORDPRESSES_FILE
end end
end end
context 'when the :vulns_file is already set' do context 'when the :db_file is already set' do
it 'returns it' do it 'returns it' do
@expected = 'test.json' @expected = 'test.json'
subject.vulns_file = @expected subject.db_file = @expected
end end
end end
end end
describe '#identifier' do describe '#identifier' do
its(:identifier) { is_expected.to eq '1.2' } its(:identifier) { should eq '1.2' }
end end
end end

View File

@@ -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."
@@ -133,7 +137,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 'The remote website is up, but does not seem to be running WordPress.'
end end
end end
@@ -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
@@ -273,15 +277,29 @@ def main
# 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} ...") if wpscan_options.enumerate_only_vulnerable_plugins
puts info('Enumerating installed plugins (only ones with known vulnerabilities) ...')
plugin_enumeration_type = :vulnerable
end
if wpscan_options.enumerate_plugins
puts info('Enumerating installed plugins (only ones marked as popular) ...')
plugin_enumeration_type = :popular
end
if wpscan_options.enumerate_all_plugins
puts info('Enumerating all plugins (may take a while and use a lot of system resources) ...')
plugin_enumeration_type = :all
end
puts puts
wp_plugins = WpPlugins.aggressive_detection(wp_target, wp_plugins = WpPlugins.aggressive_detection(wp_target,
enum_options.merge( enum_options.merge(
file: wpscan_options.enumerate_all_plugins ? PLUGINS_FULL_FILE : PLUGINS_FILE, file: PLUGINS_FILE,
only_vulnerable: wpscan_options.enumerate_only_vulnerable_plugins || false type: plugin_enumeration_type
) )
) )
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:")
@@ -295,13 +313,26 @@ def main
# 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} ...") if wpscan_options.enumerate_only_vulnerable_themes
puts info('Enumerating installed themes (only ones with known vulnerabilities) ...')
theme_enumeration_type = :vulnerable
end
if wpscan_options.enumerate_themes
puts info('Enumerating installed themes (only ones marked as popular) ...')
theme_enumeration_type = :popular
end
if wpscan_options.enumerate_all_themes
puts info('Enumerating all themes (may take a while and use a lot of system resources) ...')
theme_enumeration_type = :all
end
puts puts
wp_themes = WpThemes.aggressive_detection(wp_target, wp_themes = WpThemes.aggressive_detection(wp_target,
enum_options.merge( enum_options.merge(
file: wpscan_options.enumerate_all_themes ? THEMES_FULL_FILE : THEMES_FILE, file: THEMES_FILE,
only_vulnerable: wpscan_options.enumerate_only_vulnerable_themes || false type: theme_enumeration_type
) )
) )
puts puts
@@ -413,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