Compare commits
267 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f832e27b49 | ||
|
|
6ce29f73c5 | ||
|
|
920338fb62 | ||
|
|
49d0a9e6d9 | ||
|
|
fe401e622b | ||
|
|
6e32cb0db2 | ||
|
|
73171eb39d | ||
|
|
2e05f4171e | ||
|
|
75b8c303e2 | ||
|
|
bd7a493f1c | ||
|
|
9dada7c8f4 | ||
|
|
fe7aede458 | ||
|
|
cdf2b38780 | ||
|
|
a09dbab6a8 | ||
|
|
49a6d275d2 | ||
|
|
8192a4a215 | ||
|
|
1d6593fd4d | ||
|
|
bf99e31e70 | ||
|
|
5386496bdc | ||
|
|
6451510449 | ||
|
|
cd68aa719c | ||
|
|
b328dc4ff9 | ||
|
|
1e1c79aa56 | ||
|
|
08650ce156 | ||
|
|
a1929719f3 | ||
|
|
d34da72cd3 | ||
|
|
816b18b604 | ||
|
|
a78a13bf3f | ||
|
|
33f8aaf1dc | ||
|
|
26ab95d822 | ||
|
|
cea01d8aa0 | ||
|
|
0e61f1e284 | ||
|
|
ddef061b90 | ||
|
|
addeab8947 | ||
|
|
55dc665404 | ||
|
|
8f8538e9e9 | ||
|
|
348ca55bee | ||
|
|
1bb5bc7f33 | ||
|
|
3be5e1fcf5 | ||
|
|
9df8cc9243 | ||
|
|
e28c84aa34 | ||
|
|
7db6b54761 | ||
|
|
e3a06f5694 | ||
|
|
7c5d15e098 | ||
|
|
d683c0f151 | ||
|
|
1e67fa26ff | ||
|
|
0ae6ef59ec | ||
|
|
e27ef40e0f | ||
|
|
380760d028 | ||
|
|
18cfdafc19 | ||
|
|
0934a2e329 | ||
|
|
d1a320324e | ||
|
|
361c96d746 | ||
|
|
e7dbf9278d | ||
|
|
6564fddb27 | ||
|
|
d382874e86 | ||
|
|
91b30bee9f | ||
|
|
7804aad776 | ||
|
|
b7552ac8aa | ||
|
|
a76c94cccf | ||
|
|
c0ae5c7cad | ||
|
|
cc55b39b83 | ||
|
|
d8a6884ab6 | ||
|
|
5ce3581386 | ||
|
|
2208f2a8c0 | ||
|
|
a4a14c7e63 | ||
|
|
aa464b476c | ||
|
|
3c92712a6e | ||
|
|
fd0c47f5d7 | ||
|
|
c03a44d225 | ||
|
|
d31d45ba71 | ||
|
|
db528b27f4 | ||
|
|
e6d29f6f18 | ||
|
|
e4d6b988ef | ||
|
|
ec68291bf0 | ||
|
|
3a6a451db1 | ||
|
|
7ec095d708 | ||
|
|
57f6206aee | ||
|
|
390f10e83f | ||
|
|
8727935cb2 | ||
|
|
d0e868f556 | ||
|
|
01c357e146 | ||
|
|
a0fed4a9d0 | ||
|
|
c4aed0ec89 | ||
|
|
cc737090a2 | ||
|
|
1652c09e95 | ||
|
|
2538b88579 | ||
|
|
8c2eb63840 | ||
|
|
36df5ee6e4 | ||
|
|
9720b4edf1 | ||
|
|
13d35b7607 | ||
|
|
13c2c51cfd | ||
|
|
f43175b0c3 | ||
|
|
1508aba8b2 | ||
|
|
5414ab05e5 | ||
|
|
bd5d2db634 | ||
|
|
3259dd29d8 | ||
|
|
6e56013a95 | ||
|
|
252f762209 | ||
|
|
15c0448cf1 | ||
|
|
4c800bacaa | ||
|
|
5902a483b4 | ||
|
|
ca73e4b93e | ||
|
|
ace64d88ce | ||
|
|
4cc9f7c8b5 | ||
|
|
f4f1390b67 | ||
|
|
14115761f9 | ||
|
|
ac3409e376 | ||
|
|
86a73229c0 | ||
|
|
cc41b96e88 | ||
|
|
e16c5584d1 | ||
|
|
94bab3f550 | ||
|
|
9d04b23fb2 | ||
|
|
2657e5050f | ||
|
|
3d6e5b2b9e | ||
|
|
bdd6b9727d | ||
|
|
6c8172c7cf | ||
|
|
ae5bae9899 | ||
|
|
b6bf306042 | ||
|
|
9c5196dfec | ||
|
|
3d7b8592ea | ||
|
|
e03f7691f2 | ||
|
|
7a54ac62d6 | ||
|
|
8db06d37d2 | ||
|
|
5ee5e76544 | ||
|
|
090cd999cb | ||
|
|
50b75354e0 | ||
|
|
c7b6b25851 | ||
|
|
b931df654d | ||
|
|
b5d5c4177d | ||
|
|
b22550ea55 | ||
|
|
04d50ebea5 | ||
|
|
202180909c | ||
|
|
0d806e6d74 | ||
|
|
54f31ebe7f | ||
|
|
227a39d2fa | ||
|
|
99d8faa38b | ||
|
|
9a7afe1549 | ||
|
|
e6751e0d89 | ||
|
|
371f1df830 | ||
|
|
8e1ba352ee | ||
|
|
7ebfe42eb2 | ||
|
|
df514d3b9f | ||
|
|
acae16e7ee | ||
|
|
deb8508ea5 | ||
|
|
a4bbf41086 | ||
|
|
4fbc535b0c | ||
|
|
36f6f98ce7 | ||
|
|
21cc7d604c | ||
|
|
44207161e6 | ||
|
|
dc20ef0754 | ||
|
|
413ee7a6d3 | ||
|
|
5b94714ca7 | ||
|
|
3675fe1ed7 | ||
|
|
e074a03c40 | ||
|
|
a7860f72a2 | ||
|
|
4b587593ee | ||
|
|
0aa8a97070 | ||
|
|
3c16f84853 | ||
|
|
346898e549 | ||
|
|
bcef4b2de7 | ||
|
|
e42bf7fd7c | ||
|
|
48cd0602d8 | ||
|
|
814e837ae5 | ||
|
|
a58b34eba8 | ||
|
|
7d790f8f79 | ||
|
|
7cf06f4989 | ||
|
|
61381b7168 | ||
|
|
df598c5900 | ||
|
|
aed74e029a | ||
|
|
6e01e1b9da | ||
|
|
42f278aafe | ||
|
|
884f64addb | ||
|
|
0c9cf4ddd5 | ||
|
|
f6dfe0e8dd | ||
|
|
9f4ca1add7 | ||
|
|
1f6edc5852 | ||
|
|
a74017f595 | ||
|
|
89bc7609ea | ||
|
|
2c93c8ef6d | ||
|
|
bfe370fa50 | ||
|
|
3b4850e1ba | ||
|
|
b2d1c25b8e | ||
|
|
093598ac99 | ||
|
|
585d22be46 | ||
|
|
9361cf4b00 | ||
|
|
298e9130dd | ||
|
|
41ae47f065 | ||
|
|
41f7fe1554 | ||
|
|
965be1c0f3 | ||
|
|
fa8ac37e8b | ||
|
|
d7975b6192 | ||
|
|
0a0fe55427 | ||
|
|
8e08a20178 | ||
|
|
9dd44808ec | ||
|
|
507cf1d511 | ||
|
|
53f3ce8b1f | ||
|
|
2d39e5b1fa | ||
|
|
60716dcf81 | ||
|
|
82141c2535 | ||
|
|
3d6de3fe75 | ||
|
|
03ab396353 | ||
|
|
6221601376 | ||
|
|
71fdef45c9 | ||
|
|
147a9e4968 | ||
|
|
8f7b56da32 | ||
|
|
4ef2452083 | ||
|
|
70cfa03ee8 | ||
|
|
5bd3d4fd96 | ||
|
|
c0fe02efb9 | ||
|
|
b0f4843526 | ||
|
|
a9e161268c | ||
|
|
cbad8857bd | ||
|
|
5adefda286 | ||
|
|
265bfcd7c8 | ||
|
|
b81a4987d9 | ||
|
|
6b9c9eb0ed | ||
|
|
4f82d618dc | ||
|
|
b7f7bdb9ac | ||
|
|
c5136fd330 | ||
|
|
e7e0e886fc | ||
|
|
42e8ab1680 | ||
|
|
ab7b7de60a | ||
|
|
21221d48d0 | ||
|
|
1f1a190c84 | ||
|
|
82d79c4662 | ||
|
|
08771a6d5d | ||
|
|
e01d18f224 | ||
|
|
8496650542 | ||
|
|
399245cd0f | ||
|
|
adfa5dddcf | ||
|
|
85971e0e91 | ||
|
|
3a3376ec41 | ||
|
|
d988b6ccbf | ||
|
|
6654f446a4 | ||
|
|
88808db9a5 | ||
|
|
dfad0fd6bd | ||
|
|
3fe49a24c7 | ||
|
|
ac609445fb | ||
|
|
0223f74a53 | ||
|
|
607a5b3fda | ||
|
|
e3ac331a71 | ||
|
|
e09b4cc76d | ||
|
|
c24ed707ef | ||
|
|
a8c55ddee3 | ||
|
|
e080835224 | ||
|
|
2fe675abce | ||
|
|
d230221999 | ||
|
|
91a01265e5 | ||
|
|
77286301a7 | ||
|
|
7c39827c16 | ||
|
|
8f789994eb | ||
|
|
79cb9c8142 | ||
|
|
de1d047c08 | ||
|
|
8252cb486b | ||
|
|
fb8ad72335 | ||
|
|
bc4f0c002b | ||
|
|
0a53c52645 | ||
|
|
7941a8accb | ||
|
|
5389923b34 | ||
|
|
9c1149cb25 | ||
|
|
c5130de805 | ||
|
|
020633503b | ||
|
|
74b9776801 | ||
|
|
5a605d686c | ||
|
|
4ba9bdf605 | ||
|
|
3f647348c3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ log.txt
|
||||
debug.log
|
||||
wordlist.txt
|
||||
rspec_results.html
|
||||
data/
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.1.3
|
||||
2.3.1
|
||||
|
||||
28
.travis.yml
28
.travis.yml
@@ -1,15 +1,23 @@
|
||||
language: ruby
|
||||
sudo: false
|
||||
cache: bundler
|
||||
rvm:
|
||||
- 1.9.2
|
||||
- 1.9.3
|
||||
- 2.0.0
|
||||
- 2.1.0
|
||||
- 2.1.1
|
||||
- 2.1.2
|
||||
# Still not in Travis :(
|
||||
# - 2.1.9
|
||||
- 2.2.0
|
||||
- 2.2.1
|
||||
- 2.2.2
|
||||
- 2.2.3
|
||||
- 2.2.4
|
||||
- 2.3.0
|
||||
- 2.3.1
|
||||
before_install:
|
||||
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||
script: bundle exec rspec
|
||||
notifications:
|
||||
email:
|
||||
- wpscanteam@gmail.com
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: 1.9.2
|
||||
- team@wpscan.org
|
||||
# do not build gh-pages branch
|
||||
branches:
|
||||
except:
|
||||
- gh-pages
|
||||
|
||||
188
CHANGELOG.md
188
CHANGELOG.md
@@ -1,6 +1,191 @@
|
||||
# Changelog
|
||||
## Master
|
||||
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.5.1...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
|
||||
Released: 2015-06-22
|
||||
|
||||
New
|
||||
* Warn the user to update his DB files
|
||||
* Added last db update to --version option (see #815)
|
||||
* Add db checksum to verbose logging during update
|
||||
* Option to hide banner
|
||||
* Continue if user chooses not to update + db exists
|
||||
* Don't update if user chooses default + no DBs exist
|
||||
* Updates request timeout values to realistic ones (and in seconds)
|
||||
|
||||
Removed
|
||||
* Removed `Time.parse('2000-01-01')` expedient
|
||||
* Removed unnecessary 'return' and '()'
|
||||
* Removed debug output
|
||||
* Removed wpstools
|
||||
|
||||
General core
|
||||
* Update to Ruby 2.2.2
|
||||
* Switch to mitre
|
||||
* Install bundler gem README
|
||||
* Switch from gnutls to openssl
|
||||
|
||||
Fixed issues
|
||||
* Fix #789 - Add blackarch to readme
|
||||
* Fix #790 - Consider the target down after 30 requests timed out requests instead of 10
|
||||
* Fix #791 - Rogue character causing the scan of non-wordpress site to crash
|
||||
* Fix #792 - Adds the HttpError exception
|
||||
* Fix #795 - Remove GHOST warning
|
||||
* Fix #796 - Do not swallow exit code
|
||||
* Fix #797 - Increases the timeout values
|
||||
* Fix #801 - Forces UTF-8 encoding when enumerating usernames
|
||||
* Fix #803 - Increases default connect-timeout to 10s
|
||||
* Fix #804 - Updates the Theme detection pattern
|
||||
* Fix #816 - Ignores potential non version chars in theme version detection
|
||||
* Fix #819 - Removes potential spaces in robots.txt entries
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 98
|
||||
* Total vulnerable plugins: 1076
|
||||
* Total vulnerable themes: 361
|
||||
* Total version vulnerabilities: 1104
|
||||
* Total plugin vulnerabilities: 1763
|
||||
* Total theme vulnerabilities: 443
|
||||
|
||||
## Version 2.7
|
||||
Released: 2015-03-16
|
||||
|
||||
New
|
||||
* Detects version in release date format
|
||||
* Copyrights updated
|
||||
* WP version detection from stylesheets
|
||||
* New license
|
||||
* Global HTTP request counter
|
||||
* Add security-protection plugin detection
|
||||
* Add GHOST warning if XMLRPC enabled
|
||||
* Update databases from wpvulndb.com
|
||||
* Enumerate usernames from WP <= 3.0 (thanks berotti3)
|
||||
|
||||
Removed
|
||||
* README.txt
|
||||
|
||||
General core
|
||||
* Update to Ruby 2.2.1
|
||||
* Update to Ruby 2.2.0
|
||||
* Add addressable gem
|
||||
* Update Typhoeus gem to 0.7.0
|
||||
* IDN support: encode non-ascii domain names (thanks dctabuyz)
|
||||
* Improve page hash calculation (thanks dctabuyz)
|
||||
* Version detection regex improved
|
||||
|
||||
Fixed issues
|
||||
* Fix #745 - Plugin version pattern in readme.txt file not detected
|
||||
* Fix #746 - Add a global counter for all active requests to server.
|
||||
* Fix #747 - Add 'security-protection' plugin to wp_login_protection module
|
||||
* Fix #753 - undefined method `round' for "10":String for request or connect timeouts
|
||||
* Fix #760 - typhoeus issue (infinite loop)
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 89
|
||||
* Total vulnerable plugins: 953
|
||||
* Total vulnerable themes: 329
|
||||
* Total version vulnerabilities: 1070
|
||||
* Total plugin vulnerabilities: 1451
|
||||
* Total theme vulnerabilities: 378
|
||||
|
||||
## Version 2.6
|
||||
Released: 2014-12-19
|
||||
|
||||
New
|
||||
* Updates the readmes to reflect the new --usernames option
|
||||
* Improves plugin/theme version detection by looking at the "Version:"
|
||||
* Solution to avoid mandatory blank newline at the end of the wordlist
|
||||
* Add check for valid credentials
|
||||
* Add Sucuri sponsor to banner
|
||||
* Add protocol to sucuri url in banner
|
||||
* Add response code to proxy error output
|
||||
* Add a statement about mandatory newlines at the end of list
|
||||
* Give warning if default username 'admin' is still used
|
||||
* License amendment to make it more clear about value added usage
|
||||
|
||||
Removed
|
||||
* remove malwares
|
||||
* remove malware folder
|
||||
* Removes the theme version check from the readme, unrealistic scenario
|
||||
|
||||
General core
|
||||
* Update to Ruby 2.1.5 and travis
|
||||
* Prevent parent theme infinite loop
|
||||
* Fixes the progressbar being overriden by next brute forcing attempts
|
||||
|
||||
Fixed issues
|
||||
* Fix UTF-8 encode on security db file download
|
||||
* Fix #703 - Disable logging by default. Implement log option.
|
||||
* Fix #705 - Installation instructions for Ubuntu < 14.04 apparently incomplete
|
||||
* Fix #717 - Expand on readme.html finding output
|
||||
* Fix #716 - Adds the --version in the help
|
||||
* Fix #715 - Add new updating info to docs
|
||||
* Fix #727 - WpItems detection: Perform the passive check and filter only vulnerable results at the end if required
|
||||
* Fix #737 - Adds some readme files to check for plugin versions
|
||||
* Fix #739 - Adds the --usernames option
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 88
|
||||
* Total vulnerable plugins: 901
|
||||
* Total vulnerable themes: 313
|
||||
* Total version vulnerabilities: 1050
|
||||
* Total plugin vulnerabilities: 1355
|
||||
* Total theme vulnerabilities: 349
|
||||
|
||||
## Version 2.5.1
|
||||
Released: 2014-09-29
|
||||
@@ -325,4 +510,3 @@ Fixed issues
|
||||
|
||||
## Version 2.1
|
||||
Released 2013-3-4
|
||||
|
||||
|
||||
6
CREDITS
6
CREDITS
@@ -1,12 +1,12 @@
|
||||
**CREDITS**
|
||||
|
||||
This file is to give credit to WPScan's contributors. If you feel your name should be in here, email ryandewhurst at gmail.
|
||||
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*
|
||||
|
||||
Erwan.LR - @erwan_lr - (Project Developer)
|
||||
Christian Mehlmauer - @_FireFart_ - (Project Developer)
|
||||
Peter van der Laan - pvdl - (Vuln Hunter and Code Cleaner)
|
||||
Peter van der Laan - pvdl - (Project Developer)
|
||||
Ryan Dewhurst - @ethicalhack3r (Project Lead)
|
||||
|
||||
*Other Contributors*
|
||||
@@ -18,4 +18,4 @@ Callum Pember - Implemented proxy support - callumpember at gmail.com
|
||||
g0tmi1k - Additional timthumb checks + bug reports
|
||||
Melvin Lammerts - Reported a couple of fake vulnerabilities - melvin at 12k.nl
|
||||
Paolo Perego - @thesp0nge - Basic authentication
|
||||
Gianluca Brindisi - @gbrindisi - Project Developer
|
||||
Gianluca Brindisi - @gbrindisi - Ex Project Developer
|
||||
|
||||
14
Gemfile
14
Gemfile
@@ -1,14 +1,18 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'typhoeus', '~>0.6.8'
|
||||
gem 'nokogiri'
|
||||
gem 'json'
|
||||
gem 'terminal-table'
|
||||
gem 'typhoeus', '>=0.8.0'
|
||||
gem 'nokogiri', '>=1.6.7.1'
|
||||
gem 'addressable'
|
||||
gem 'yajl-ruby' # Better JSON parser regarding memory usage
|
||||
# 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'
|
||||
|
||||
group :test do
|
||||
gem 'webmock', '>=1.17.2'
|
||||
gem 'simplecov'
|
||||
gem 'rspec', '~>3.0'
|
||||
gem 'rspec', '>=3.3.0'
|
||||
gem 'rspec-its'
|
||||
end
|
||||
|
||||
74
LICENSE
74
LICENSE
@@ -1,20 +1,70 @@
|
||||
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team.
|
||||
WPScan Public Source License
|
||||
|
||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License.
|
||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2016 WPScan Team.
|
||||
|
||||
Cases of commercialization are:
|
||||
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.
|
||||
|
||||
- Using WPScan to provide commercial managed/Software-as-a-Service services.
|
||||
- Distributing WPScan as a commercial product or as part of one.
|
||||
1. Definitions
|
||||
|
||||
Cases which do not require a commercial license, and thus fall under the terms of GNU General Public License, include (but are not limited to):
|
||||
1.1 “License” means this document.
|
||||
1.2 “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
|
||||
1.3 “WPScan Team” means WPScan’s core developers, an updated list of whom can be found within the CREDITS file.
|
||||
|
||||
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause.
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
2. Commercialization
|
||||
|
||||
If you need to acquire a commercial license or are unsure about whether you need to acquire a commercial license, please get in touch, we will be happy to clarify things for you and work with you to accommodate your requirements.
|
||||
A commercial use is one intended for commercial advantage or monetary compensation.
|
||||
|
||||
wpscanteam at gmail.com
|
||||
Example cases of commercialization are:
|
||||
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
- Using WPScan to provide commercial managed/Software-as-a-Service services.
|
||||
- Distributing WPScan as a commercial product or as part of one.
|
||||
- Using WPScan as a value added service/product.
|
||||
|
||||
Example cases which do not require a commercial license, and thus fall under the terms set out below, include (but are not limited to):
|
||||
|
||||
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit.
|
||||
- Penetration Testing Linux Distributions including but not limited to Kali Linux, SamuraiWTF, BackBox Linux.
|
||||
- Using WPScan to test your own systems.
|
||||
- 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 - 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.
|
||||
|
||||
Free-use Terms and Conditions;
|
||||
|
||||
3. Redistribution
|
||||
|
||||
Redistribution is permitted under the following conditions:
|
||||
|
||||
- Unmodified License is provided with WPScan.
|
||||
- Unmodified Copyright notices are provided with WPScan.
|
||||
- Does not conflict with the commercialization clause.
|
||||
|
||||
4. Copying
|
||||
|
||||
Copying is permitted so long as it does not conflict with the Redistribution clause.
|
||||
|
||||
5. Modification
|
||||
|
||||
Modification is permitted so long as it does not conflict with the Redistribution clause.
|
||||
|
||||
6. Contributions
|
||||
|
||||
Any Contributions assume the Contributor grants the WPScan Team the unlimited, non-exclusive right to reuse, modify and relicense the Contributor's content.
|
||||
|
||||
7. Support
|
||||
|
||||
WPScan is provided under an AS-IS basis and without any support, updates or maintenance. Support, updates and maintenance may be given according to the sole discretion of the WPScan Team.
|
||||
|
||||
8. Disclaimer of Warranty
|
||||
|
||||
WPScan is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the WPScan is free of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
|
||||
9. Limitation of Liability
|
||||
|
||||
To the extent permitted under Law, WPScan is provided under an AS-IS basis. The WPScan Team shall never, and without any limit, be liable for any damage, cost, expense or any other payment incurred as a result of WPScan's actions, failure, bugs and/or any other interaction between WPScan and end-equipment, computers, other software or any 3rd party, end-equipment, computer or services.
|
||||
|
||||
10. Disclaimer
|
||||
|
||||
Running WPScan against websites without prior mutual consent may be illegal in your country. The WPScan Team accept no liability and are not responsible for any misuse or damage caused by WPScan.
|
||||
|
||||
285
README
285
README
@@ -1,285 +0,0 @@
|
||||
__________________________________________________
|
||||
__ _______ _____
|
||||
\ \ / / __ \ / ____|
|
||||
\ \ /\ / /| |__) | (___ ___ __ _ _ __
|
||||
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
|
||||
\ /\ / | | ____) | (__| (_| | | | |
|
||||
\/ \/ |_| |_____/ \___|\__,_|_| |_|
|
||||
__________________________________________________
|
||||
|
||||
==LICENSE==
|
||||
|
||||
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team.
|
||||
|
||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License.
|
||||
|
||||
Cases of commercialization are:
|
||||
|
||||
- Using WPScan to provide commercial managed/Software-as-a-Service services.
|
||||
- Distributing WPScan as a commercial product or as part of one.
|
||||
|
||||
Cases which do not require a commercial license, and thus fall under the terms of GNU General Public License, include (but are not limited to):
|
||||
|
||||
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause.
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
|
||||
If you need to acquire a commercial license or are unsure about whether you need to acquire a commercial license, please get in touch, we will be happy to clarify things for you and work with you to accommodate your requirements.
|
||||
|
||||
wpscanteam at gmail.com
|
||||
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
==INSTALL==
|
||||
|
||||
WPScan comes pre-installed on the following Linux distributions:
|
||||
|
||||
* BackBox Linux
|
||||
* Kali Linux
|
||||
* Pentoo
|
||||
* SamuraiWTF
|
||||
* ArchAssault
|
||||
|
||||
Prerequisites:
|
||||
|
||||
* Windows not supported
|
||||
* Ruby >= 1.9.2 - Recommended: 2.1.2
|
||||
* Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
|
||||
* RubyGems - Recommended: latest
|
||||
* Git
|
||||
|
||||
-> Installing on Ubuntu:
|
||||
|
||||
Before Ubuntu 14.04:
|
||||
|
||||
sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
|
||||
|
||||
From Ubuntu 14.04:
|
||||
|
||||
sudo apt-get install libcurl4-gnutls-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential
|
||||
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
-> Installing on Debian:
|
||||
|
||||
sudo apt-get install git ruby ruby-dev libcurl4-gnutls-dev make
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler
|
||||
bundle install --without test --path vendor/bundle
|
||||
|
||||
-> Installing on Fedora:
|
||||
|
||||
sudo yum install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
-> Installing on Archlinux:
|
||||
|
||||
pacman -Syu ruby
|
||||
pacman -Syu libyaml
|
||||
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
gem install typhoeus
|
||||
gem install nokogiri
|
||||
|
||||
-> Installing on Mac OS X:
|
||||
|
||||
Apple Xcode, Command Line Tools and the libffi are needed (to be able to install the FFI gem), See http://stackoverflow.com/questions/17775115/cant-setup-ruby-environment-installing-fii-gem-error
|
||||
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && sudo bundle install --without test
|
||||
|
||||
-> Installing with RVM:
|
||||
|
||||
cd ~
|
||||
curl -sSL https://get.rvm.io | bash -s stable
|
||||
source ~/.rvm/scripts/rvm
|
||||
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
|
||||
rvm install 2.1.2
|
||||
rvm use 2.1.2 --default
|
||||
echo "gem: --no-ri --no-rdoc" > ~/.gemrc
|
||||
gem install bundler
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
bundle install --without test
|
||||
|
||||
==KNOWN ISSUES==
|
||||
|
||||
- Typhoeus segmentation fault:
|
||||
Update cURL to version => 7.21 (may have to install from source)
|
||||
|
||||
- Proxy not working:
|
||||
Update cURL to version => 7.21.7 (may have to install from source).
|
||||
|
||||
Installation from sources :
|
||||
- Grab the sources from http://curl.haxx.se/download.html
|
||||
- Decompress the archive
|
||||
- Open the folder with the extracted files
|
||||
- Run ./configure
|
||||
- Run make
|
||||
- Run sudo make install
|
||||
- Run sudo ldconfig
|
||||
|
||||
- cannot load such file -- readline:
|
||||
Run sudo aptitude install libreadline5-dev libncurses5-dev
|
||||
|
||||
Then, open the directory of the readline gem (you have to locate it)
|
||||
|
||||
cd ~/.rvm/src/ruby-1.9.2-p180/ext/readline
|
||||
ruby extconf.rb
|
||||
make
|
||||
make install
|
||||
|
||||
See http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/ for more details
|
||||
|
||||
- no such file to load -- rubygems
|
||||
Run update-alternatives --config ruby
|
||||
And select your ruby version
|
||||
|
||||
See https://github.com/wpscanteam/wpscan/issues/148
|
||||
|
||||
|
||||
==WPSCAN ARGUMENTS==
|
||||
|
||||
--update Update the databases.
|
||||
|
||||
--url | -u <target url> The WordPress URL/domain to scan.
|
||||
|
||||
--force | -f Forces WPScan to not check if the remote site is running WordPress.
|
||||
|
||||
--enumerate | -e [option(s)] Enumeration.
|
||||
option :
|
||||
u usernames from id 1 to 10
|
||||
u[10-20] usernames from id 10 to 20 (you must write [] chars)
|
||||
p plugins
|
||||
vp only vulnerable plugins
|
||||
ap all plugins (can take a long time)
|
||||
tt timthumbs
|
||||
t themes
|
||||
vt only vulnerable themes
|
||||
at all themes (can take a long time)
|
||||
Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
|
||||
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
|
||||
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
|
||||
|
||||
--user-agent | -a <User-Agent> Use the specified 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
|
||||
|
||||
--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 bruter and do the brute.
|
||||
|
||||
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
|
||||
|
||||
--username | -U <username> Only brute force the supplied username.
|
||||
|
||||
--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 behaviour.
|
||||
|
||||
--no-color Do not use colors in the output.
|
||||
|
||||
==WPSCAN EXAMPLES==
|
||||
|
||||
Do 'non-intrusive' checks...
|
||||
|
||||
ruby wpscan.rb --url www.example.com
|
||||
|
||||
Do wordlist password brute force on enumerated users using 50 threads...
|
||||
|
||||
ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --threads 50
|
||||
|
||||
Do wordlist password brute force on the 'admin' username only...
|
||||
|
||||
ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --username admin
|
||||
|
||||
Enumerate installed plugins...
|
||||
|
||||
ruby wpscan.rb --url www.example.com --enumerate p
|
||||
|
||||
Run all enumeration tools...
|
||||
|
||||
ruby wpscan.rb --url www.example.com --enumerate
|
||||
|
||||
Use custom content directory...
|
||||
|
||||
ruby wpscan.rb -u www.example.com --wp-content-dir custom-content
|
||||
|
||||
Update WPScan's databases...
|
||||
|
||||
ruby wpscan.rb --update
|
||||
|
||||
Debug output...
|
||||
|
||||
ruby wpscan.rb --url www.example.com --debug-output 2>debug.log
|
||||
|
||||
==WPSTOOLS ARGUMENTS==
|
||||
|
||||
-v, --verbose Verbose output
|
||||
--check-vuln-ref-urls, --cvru Check all the vulnerabilities reference urls for 404
|
||||
--check-local-vulnerable-files, --clvf LOCAL_DIRECTORY Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells
|
||||
s, --stats Show WpScan Database statistics.
|
||||
--spellcheck, --sc Check all files for common spelling mistakes.
|
||||
|
||||
==WPSTOOLS EXAMPLES==
|
||||
|
||||
Locally scan a wordpress installation for vulnerable files or shells:
|
||||
ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/
|
||||
|
||||
Or check https://github.com/fgeek/pyfiscan project.
|
||||
|
||||
===PROJECT HOME===
|
||||
|
||||
www.wpscan.org
|
||||
|
||||
===REPOSITORY===
|
||||
|
||||
https://github.com/wpscanteam/wpscan
|
||||
|
||||
===ISSUES===
|
||||
|
||||
https://github.com/wpscanteam/wpscan/issues
|
||||
|
||||
===DEVELOPER DOCUMENTATION===
|
||||
|
||||
http://rdoc.info/github/wpscanteam/wpscan/frames
|
||||
|
||||
===SPONSOR===
|
||||
|
||||
WPScan is sponsored by the RandomStorm Open Source Initiative.
|
||||
|
||||
Visit RandomStorm at http://www.randomstorm.com
|
||||
222
README.md
222
README.md
@@ -1,29 +1,84 @@
|
||||

|
||||
|
||||
[](https://travis-ci.org/wpscanteam/wpscan)
|
||||
|
||||
[](https://travis-ci.org/wpscanteam/wpscan)
|
||||
[](https://codeclimate.com/github/wpscanteam/wpscan)
|
||||
[](https://gemnasium.com/wpscanteam/wpscan)
|
||||
|
||||
#### LICENSE
|
||||
|
||||
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team.
|
||||
#### WPScan Public Source License
|
||||
|
||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License.
|
||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2016 WPScan Team.
|
||||
|
||||
Cases of commercialization are:
|
||||
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.
|
||||
|
||||
- Using WPScan to provide commercial managed/Software-as-a-Service services.
|
||||
- Distributing WPScan as a commercial product or as part of one.
|
||||
##### 1. Definitions
|
||||
|
||||
Cases which do not require a commercial license, and thus fall under the terms of GNU General Public License, include (but are not limited to):
|
||||
1.1 "License" means this document.
|
||||
|
||||
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause.
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
1.2 "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
|
||||
|
||||
If you need to acquire a commercial license or are unsure about whether you need to acquire a commercial license, please get in touch, we will be happy to clarify things for you and work with you to accommodate your requirements.
|
||||
1.3 "WPScan Team" means WPScan’s core developers, an updated list of whom can be found within the CREDITS file.
|
||||
|
||||
wpscanteam at gmail.com
|
||||
##### 2. Commercialization
|
||||
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
A commercial use is one intended for commercial advantage or monetary compensation.
|
||||
|
||||
Example cases of commercialization are:
|
||||
|
||||
- Using WPScan to provide commercial managed/Software-as-a-Service services.
|
||||
- Distributing WPScan as a commercial product or as part of one.
|
||||
- Using WPScan as a value added service/product.
|
||||
|
||||
Example cases which do not require a commercial license, and thus fall under the terms set out below, include (but are not limited to):
|
||||
|
||||
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit.
|
||||
- Penetration Testing Linux Distributions including but not limited to Kali Linux, SamuraiWTF, BackBox Linux.
|
||||
- Using WPScan to test your own systems.
|
||||
- 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 - 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.
|
||||
|
||||
Free-use Terms and Conditions;
|
||||
|
||||
##### 3. Redistribution
|
||||
|
||||
Redistribution is permitted under the following conditions:
|
||||
|
||||
- Unmodified License is provided with WPScan.
|
||||
- Unmodified Copyright notices are provided with WPScan.
|
||||
- Does not conflict with the commercialization clause.
|
||||
|
||||
##### 4. Copying
|
||||
|
||||
Copying is permitted so long as it does not conflict with the Redistribution clause.
|
||||
|
||||
##### 5. Modification
|
||||
|
||||
Modification is permitted so long as it does not conflict with the Redistribution clause.
|
||||
|
||||
##### 6. Contributions
|
||||
|
||||
Any Contributions assume the Contributor grants the WPScan Team the unlimited, non-exclusive right to reuse, modify and relicense the Contributor's content.
|
||||
|
||||
##### 7. Support
|
||||
|
||||
WPScan is provided under an AS-IS basis and without any support, updates or maintenance. Support, updates and maintenance may be given according to the sole discretion of the WPScan Team.
|
||||
|
||||
##### 8. Disclaimer of Warranty
|
||||
|
||||
WPScan is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the WPScan is free of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
|
||||
##### 9. Limitation of Liability
|
||||
|
||||
To the extent permitted under Law, WPScan is provided under an AS-IS basis. The WPScan Team shall never, and without any limit, be liable for any damage, cost, expense or any other payment incurred as a result of WPScan's actions, failure, bugs and/or any other interaction between WPScan and end-equipment, computers, other software or any 3rd party, end-equipment, computer or services.
|
||||
|
||||
##### 10. Disclaimer
|
||||
|
||||
Running WPScan against websites without prior mutual consent may be illegal in your country. The WPScan Team accept no liability and are not responsible for any misuse or damage caused by WPScan.
|
||||
|
||||
#### INSTALL
|
||||
|
||||
@@ -33,33 +88,37 @@ WPScan comes pre-installed on the following Linux distributions:
|
||||
- [Kali Linux](http://www.kali.org/)
|
||||
- [Pentoo](http://www.pentoo.ch/)
|
||||
- [SamuraiWTF](http://samurai.inguardians.com/)
|
||||
- [ArchAssault](https://archassault.org/)
|
||||
- [BlackArch](http://blackarch.org/)
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Ruby >= 1.9.2 - Recommended: 2.1.2
|
||||
- Ruby >= 2.1.9 - Recommended: 2.3.1
|
||||
- Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
|
||||
- RubyGems - Recommended: latest
|
||||
- Git
|
||||
|
||||
Windows is not supported.
|
||||
If installed from Github update the code base with ```git pull```. The databases are updated with ```wpscan.rb --update```.
|
||||
|
||||
####Installing on Ubuntu:
|
||||
|
||||
Before Ubuntu 14.04:
|
||||
|
||||
sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
|
||||
sudo apt-get install libcurl4-openssl-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
From Ubuntu 14.04:
|
||||
|
||||
sudo apt-get install libcurl4-gnutls-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential
|
||||
sudo apt-get install libcurl4-openssl-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential libgmp-dev
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
####Installing on Debian:
|
||||
|
||||
sudo apt-get install git ruby ruby-dev libcurl4-gnutls-dev make
|
||||
sudo apt-get install git ruby ruby-dev libcurl4-openssl-dev make zlib1g-dev
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler
|
||||
@@ -67,7 +126,7 @@ From Ubuntu 14.04:
|
||||
|
||||
####Installing on Fedora:
|
||||
|
||||
sudo yum install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel
|
||||
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
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
@@ -90,18 +149,20 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
||||
cd wpscan
|
||||
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 ~
|
||||
curl -sSL https://get.rvm.io | bash -s stable
|
||||
source ~/.rvm/scripts/rvm
|
||||
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
|
||||
rvm install 2.1.2
|
||||
rvm use 2.1.2 --default
|
||||
rvm install 2.3.1
|
||||
rvm use 2.3.1 --default
|
||||
echo "gem: --no-ri --no-rdoc" > ~/.gemrc
|
||||
gem install bundler
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
gem install bundler
|
||||
bundle install --without test
|
||||
|
||||
#### KNOWN ISSUES
|
||||
@@ -115,7 +176,7 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
||||
Update cURL to version => 7.21.7 (may have to install from source).
|
||||
|
||||
Installation from sources :
|
||||
|
||||
|
||||
Grab the sources from http://curl.haxx.se/download.html
|
||||
Decompress the archive
|
||||
Open the folder with the extracted files
|
||||
@@ -123,19 +184,19 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
||||
Run make
|
||||
Run sudo make install
|
||||
Run sudo ldconfig
|
||||
|
||||
|
||||
|
||||
- cannot load such file -- readline:
|
||||
|
||||
sudo aptitude install libreadline5-dev libncurses5-dev
|
||||
|
||||
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
|
||||
make
|
||||
make install
|
||||
|
||||
|
||||
|
||||
See [http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/](http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/) for more details
|
||||
|
||||
@@ -149,13 +210,10 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
||||
|
||||
#### WPSCAN ARGUMENTS
|
||||
|
||||
--update Update the databases.
|
||||
|
||||
--url | -u <target url> The WordPress URL/domain to scan.
|
||||
|
||||
--force | -f Forces WPScan to not check if the remote site is running WordPress.
|
||||
|
||||
--enumerate | -e [option(s)] Enumeration.
|
||||
--update Update the database to the latest version.
|
||||
--url | -u <target url> The WordPress URL/domain to scan.
|
||||
--force | -f Forces WPScan to not check if the remote site is running WordPress.
|
||||
--enumerate | -e [option(s)] Enumeration.
|
||||
option :
|
||||
u usernames from id 1 to 10
|
||||
u[10-20] usernames from id 10 to 20 (you must write [] chars)
|
||||
@@ -169,49 +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
|
||||
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
|
||||
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
|
||||
|
||||
--user-agent | -a <User-Agent> Use the specified 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
|
||||
|
||||
--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 bruter and do the brute.
|
||||
|
||||
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
|
||||
|
||||
--username | -U <username> Only brute force the supplied username.
|
||||
|
||||
--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 behaviour.
|
||||
|
||||
--no-color Do not use colors in the output.
|
||||
--exclude-content-based "<regexp or string>"
|
||||
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.
|
||||
--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.
|
||||
--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.
|
||||
--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.
|
||||
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
|
||||
|
||||
@@ -247,30 +292,13 @@ Debug output...
|
||||
|
||||
```ruby wpscan.rb --url www.example.com --debug-output 2>debug.log```
|
||||
|
||||
#### WPSTOOLS ARGUMENTS
|
||||
|
||||
-v, --verbose Verbose output
|
||||
--check-vuln-ref-urls, --cvru Check all the vulnerabilities reference urls for 404
|
||||
--check-local-vulnerable-files, --clvf LOCAL_DIRECTORY Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells
|
||||
-s, --stats Show WpScan Database statistics.
|
||||
--spellcheck, --sc Check all files for common spelling mistakes.
|
||||
|
||||
|
||||
#### WPSTOOLS EXAMPLES
|
||||
|
||||
Locally scan a wordpress installation for vulnerable files or shells:
|
||||
|
||||
```ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/```
|
||||
|
||||
Or check [pyfiscan](https://github.com/fgeek/pyfiscan) project.
|
||||
|
||||
#### PROJECT HOME
|
||||
|
||||
[http://www.wpscan.org](http://www.wpscan.org)
|
||||
|
||||
#### VULNERABILITY DATABASE
|
||||
|
||||
[https://www.wpvulndb.com](https://www.wpvulndb.com)
|
||||
[https://wpvulndb.com](https://wpvulndb.com)
|
||||
|
||||
#### GIT REPOSITORY
|
||||
|
||||
@@ -284,6 +312,6 @@ Or check [pyfiscan](https://github.com/fgeek/pyfiscan) project.
|
||||
|
||||
[http://rdoc.info/github/wpscanteam/wpscan/frames](http://rdoc.info/github/wpscanteam/wpscan/frames)
|
||||
|
||||
#### SPONSOR
|
||||
#### SPECIAL THANKS
|
||||
|
||||
WPScan is sponsored by the [RandomStorm](http://www.randomstorm.com) Open Source Initiative.
|
||||
[RandomStorm](https://www.randomstorm.com)
|
||||
|
||||
@@ -23,7 +23,7 @@ end
|
||||
html = open(html_path).read
|
||||
examples = html.match(/(\d+) examples/)[0].to_i rescue 0
|
||||
errors = html.match(/(\d+) errors/)[0].to_i rescue 0
|
||||
if errors == 0 then
|
||||
if errors == 0
|
||||
errors = html.match(/(\d+) failure/)[0].to_i rescue 0
|
||||
end
|
||||
pending = html.match(/(\d+) pending/)[0].to_i rescue 0
|
||||
|
||||
19
dev/stats.rb
Executable file
19
dev/stats.rb
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env ruby
|
||||
# encoding: UTF-8
|
||||
|
||||
require File.dirname(__FILE__) + '/../lib/wpscan/wpscan_helper'
|
||||
|
||||
wordpress_json = json(WORDPRESSES_FILE)
|
||||
plugins_json = json(PLUGINS_FILE)
|
||||
themes_json = json(THEMES_FILE)
|
||||
|
||||
puts 'WPScan Database Statistics:'
|
||||
puts "* Total tracked wordpresses: #{wordpress_json.count}"
|
||||
puts "* Total tracked plugins: #{plugins_json.count}"
|
||||
puts "* Total tracked themes: #{themes_json.count}"
|
||||
puts "* Total vulnerable wordpresses: #{wordpress_json.select { |item| !wordpress_json[item]['vulnerabilities'].empty? }.count}"
|
||||
puts "* Total vulnerable plugins: #{plugins_json.select { |item| !plugins_json[item]['vulnerabilities'].empty? }.count}"
|
||||
puts "* Total vulnerable themes: #{themes_json.select { |item| !themes_json[item]['vulnerabilities'].empty? }.count}"
|
||||
puts "* Total wordpress vulnerabilities: #{}"
|
||||
puts "* Total plugin vulnerabilities: #{}"
|
||||
puts "* Total theme vulnerabilities: #{}"
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
"cache_ttl": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
|
||||
|
||||
"request_timeout": 2000, // 2s
|
||||
"request_timeout": 60, // 1min
|
||||
|
||||
"connect_timeout": 1000, // 1s
|
||||
"connect_timeout": 10, // 10s
|
||||
|
||||
"max_threads": 20
|
||||
}
|
||||
|
||||
@@ -17,20 +17,21 @@ class Browser
|
||||
:proxy_auth,
|
||||
:request_timeout,
|
||||
:connect_timeout,
|
||||
:cookie
|
||||
:cookie,
|
||||
:throttle
|
||||
]
|
||||
|
||||
@@instance = nil
|
||||
|
||||
attr_reader :hydra, :cache_dir
|
||||
|
||||
attr_accessor :referer, :cookie
|
||||
attr_accessor :referer, :cookie, :vhost
|
||||
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ Browser ]
|
||||
def initialize(options = {})
|
||||
@cache_dir = options[:cache_dir] || CACHE_DIR + '/browser'
|
||||
@cache_dir = options[:cache_dir] || CACHE_DIR + '/browser'
|
||||
|
||||
# sets browser defaults
|
||||
browser_defaults
|
||||
@@ -70,14 +71,14 @@ class Browser
|
||||
# sets browser default values
|
||||
#
|
||||
def browser_defaults
|
||||
@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
|
||||
@cache_ttl = 600
|
||||
# 2s
|
||||
@request_timeout = 2000
|
||||
# 1s
|
||||
@connect_timeout = 1000
|
||||
@user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
|
||||
@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
|
||||
@cache_ttl = 600
|
||||
@request_timeout = 60 # 60s
|
||||
@connect_timeout = 10 # 10s
|
||||
@user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
|
||||
@throttle = 0
|
||||
end
|
||||
|
||||
#
|
||||
@@ -88,7 +89,6 @@ class Browser
|
||||
#
|
||||
# @return [ void ]
|
||||
def load_config(config_file = nil)
|
||||
|
||||
if File.symlink?(config_file)
|
||||
raise '[ERROR] Config file is a symlink.'
|
||||
else
|
||||
@@ -101,7 +101,6 @@ class Browser
|
||||
self.send(:"#{option_name}=", data[option_name])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# @param [ String ] url
|
||||
@@ -123,11 +122,8 @@ class Browser
|
||||
)
|
||||
|
||||
if @proxy
|
||||
params = params.merge(proxy: @proxy)
|
||||
|
||||
if @proxy_auth
|
||||
params = params.merge(proxyauth: @proxy_auth)
|
||||
end
|
||||
params.merge!(proxy: @proxy)
|
||||
params.merge!(proxyauth: @proxy_auth) if @proxy_auth
|
||||
end
|
||||
|
||||
if @basic_auth
|
||||
@@ -138,19 +134,27 @@ class Browser
|
||||
)
|
||||
end
|
||||
|
||||
if vhost
|
||||
params = Browser.append_params_header_field(
|
||||
params,
|
||||
'Host',
|
||||
vhost
|
||||
)
|
||||
end
|
||||
|
||||
params.merge!(referer: referer)
|
||||
params.merge!(timeout: @request_timeout) if @request_timeout
|
||||
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout
|
||||
params.merge!(timeout: @request_timeout) if @request_timeout && !params.key?(:timeout)
|
||||
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout && !params.key?(:connecttimeout)
|
||||
|
||||
# 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
|
||||
params.merge!(maxredirs: 3) unless params.has_key?(:maxredirs)
|
||||
params.merge!(maxredirs: 3) unless params.key?(:maxredirs)
|
||||
|
||||
# Disable SSL-Certificate checks
|
||||
params.merge!(ssl_verifypeer: false)
|
||||
params.merge!(ssl_verifyhost: 0)
|
||||
params.merge!(ssl_verifypeer: false) unless params.key?(:ssl_verifypeer)
|
||||
params.merge!(ssl_verifyhost: 0) unless params.key?(:ssl_verifyhost)
|
||||
|
||||
params.merge!(cookiejar: @cache_dir + '/cookie-jar')
|
||||
params.merge!(cookiefile: @cache_dir + '/cookie-jar')
|
||||
@@ -174,5 +178,4 @@ class Browser
|
||||
end
|
||||
params
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
class Browser
|
||||
module Options
|
||||
|
||||
attr_accessor :cache_ttl, :request_timeout, :connect_timeout
|
||||
attr_reader :basic_auth, :proxy, :proxy_auth
|
||||
attr_accessor :request_timeout, :connect_timeout
|
||||
attr_reader :basic_auth, :cache_ttl, :proxy, :proxy_auth, :throttle
|
||||
attr_writer :user_agent
|
||||
|
||||
# Sets the Basic Authentification credentials
|
||||
@@ -25,6 +25,10 @@ class Browser
|
||||
end
|
||||
end
|
||||
|
||||
def cache_ttl=(ttl)
|
||||
@cache_ttl = ttl.to_i
|
||||
end
|
||||
|
||||
# @return [ Integer ]
|
||||
def max_threads
|
||||
@max_threads || 1
|
||||
@@ -82,7 +86,7 @@ class Browser
|
||||
#
|
||||
# @return [ void ]
|
||||
def request_timeout=(timeout)
|
||||
@request_timeout = timeout
|
||||
@request_timeout = timeout.to_i
|
||||
end
|
||||
|
||||
# Sets the connect timeout
|
||||
@@ -90,7 +94,12 @@ class Browser
|
||||
#
|
||||
# @return [ void ]
|
||||
def connect_timeout=(timeout)
|
||||
@connect_timeout = timeout
|
||||
@connect_timeout = timeout.to_i
|
||||
end
|
||||
|
||||
# @param [ String, Integer ] throttle
|
||||
def throttle=(throttle)
|
||||
@throttle = throttle.to_i.abs / 1000.0
|
||||
end
|
||||
|
||||
protected
|
||||
@@ -110,6 +119,5 @@ class Browser
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,39 +9,47 @@
|
||||
#
|
||||
|
||||
require 'yaml'
|
||||
require 'fileutils'
|
||||
|
||||
class CacheFileStore
|
||||
attr_reader :storage_path, :serializer
|
||||
attr_reader :storage_path, :cache_dir, :serializer
|
||||
|
||||
# The serializer must have the 2 methods .load and .dump
|
||||
# (Marshal and YAML have them)
|
||||
# YAML is Human Readable, contrary to Marshal which store in a binary format
|
||||
# Marshal does not need any "require"
|
||||
def initialize(storage_path, serializer = Marshal)
|
||||
@cache_dir = File.expand_path(storage_path)
|
||||
@storage_path = File.expand_path(File.join(storage_path, storage_dir))
|
||||
@serializer = serializer
|
||||
|
||||
# File.directory? for ruby <= 1.9 otherwise,
|
||||
# it makes more sense to do Dir.exist? :/
|
||||
unless File.directory?(@storage_path)
|
||||
unless Dir.exist?(@storage_path)
|
||||
FileUtils.mkdir_p(@storage_path)
|
||||
end
|
||||
end
|
||||
|
||||
def clean
|
||||
Dir[File.join(@storage_path, '*')].each do |f|
|
||||
File.delete(f) unless File.symlink?(f)
|
||||
# clean old directories
|
||||
Dir[File.join(@cache_dir, '*')].each do |f|
|
||||
if File.directory?(f)
|
||||
# delete directory if create time is older than 4 hours
|
||||
FileUtils.rm_rf(f) if File.mtime(f) < (Time.now - (60*240))
|
||||
else
|
||||
File.delete(f) unless File.symlink?(f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def read_entry(key)
|
||||
@serializer.load(File.read(get_entry_file_path(key)))
|
||||
rescue
|
||||
nil
|
||||
begin
|
||||
@serializer.load(File.read(get_entry_file_path(key)))
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
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|
|
||||
begin
|
||||
f.write(@serializer.dump(data_to_store))
|
||||
|
||||
@@ -1,74 +1,75 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_items/detectable'
|
||||
require 'common/collections/wp_items/output'
|
||||
|
||||
class WpItems < Array
|
||||
extend WpItems::Detectable
|
||||
include WpItems::Output
|
||||
|
||||
attr_accessor :wp_target
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
def initialize(wp_target = nil)
|
||||
self.wp_target = wp_target
|
||||
end
|
||||
|
||||
# @param [String,] argv
|
||||
#
|
||||
# @return [ void ]
|
||||
def add(*args)
|
||||
index = 0
|
||||
|
||||
until args[index].nil?
|
||||
arg = args[index]
|
||||
|
||||
if arg.is_a?(String)
|
||||
if (next_arg = args[index + 1]).is_a?(Hash)
|
||||
item = create_item(arg, next_arg)
|
||||
index += 1
|
||||
else
|
||||
item = create_item(arg)
|
||||
end
|
||||
elsif arg.is_a?(Item)
|
||||
item = arg
|
||||
else
|
||||
raise 'Invalid arguments'
|
||||
end
|
||||
|
||||
self << item
|
||||
index += 1
|
||||
end
|
||||
end
|
||||
|
||||
# @param [ String ] name
|
||||
# @param [ Hash ] attrs
|
||||
#
|
||||
# @return [ WpItem ]
|
||||
def create_item(name, attrs = {})
|
||||
raise 'wp_target must be set' unless wp_target
|
||||
|
||||
item_class.new(
|
||||
wp_target.uri,
|
||||
attrs.merge(
|
||||
name: name,
|
||||
wp_content_dir: wp_target.wp_content_dir,
|
||||
wp_plugins_dir: wp_target.wp_plugins_dir
|
||||
) { |key, oldval, newval| oldval }
|
||||
)
|
||||
end
|
||||
|
||||
# @param [ WpItems ] other
|
||||
#
|
||||
# @return [ self ]
|
||||
def +(other)
|
||||
other.each { |item| self << item }
|
||||
self
|
||||
end
|
||||
|
||||
protected
|
||||
# @return [ Class ]
|
||||
def item_class
|
||||
Object.const_get(self.class.to_s.gsub(/.$/, ''))
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_items/detectable'
|
||||
require 'common/collections/wp_items/output'
|
||||
|
||||
class WpItems < Array
|
||||
extend WpItems::Detectable
|
||||
include WpItems::Output
|
||||
|
||||
attr_accessor :wp_target
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
def initialize(wp_target = nil)
|
||||
self.wp_target = wp_target
|
||||
end
|
||||
|
||||
# @param [String] args
|
||||
#
|
||||
# @return [ void ]
|
||||
def add(*args)
|
||||
index = 0
|
||||
|
||||
until args[index].nil?
|
||||
arg = args[index]
|
||||
|
||||
if arg.is_a?(String)
|
||||
if (next_arg = args[index + 1]).is_a?(Hash)
|
||||
item = create_item(arg, next_arg)
|
||||
index += 1
|
||||
else
|
||||
item = create_item(arg)
|
||||
end
|
||||
elsif arg.is_a?(Item)
|
||||
item = arg
|
||||
else
|
||||
raise 'Invalid arguments'
|
||||
end
|
||||
|
||||
self << item
|
||||
index += 1
|
||||
end
|
||||
end
|
||||
|
||||
# @param [ String ] name
|
||||
# @param [ Hash ] attrs
|
||||
#
|
||||
# @return [ WpItem ]
|
||||
def create_item(name, attrs = {})
|
||||
raise 'wp_target must be set' unless wp_target
|
||||
|
||||
item_class.new(
|
||||
wp_target.uri,
|
||||
attrs.merge(
|
||||
name: name,
|
||||
wp_content_dir: wp_target.wp_content_dir,
|
||||
wp_plugins_dir: wp_target.wp_plugins_dir
|
||||
) { |key, oldval, newval| oldval }
|
||||
)
|
||||
end
|
||||
|
||||
# @param [ WpItems ] other
|
||||
#
|
||||
# @return [ self ]
|
||||
def +(other)
|
||||
other.each { |item| self << item }
|
||||
self
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @return [ Class ]
|
||||
def item_class
|
||||
Object.const_get(self.class.to_s.gsub(/.$/, ''))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,238 +1,236 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItems < Array
|
||||
module Detectable
|
||||
|
||||
attr_reader :vulns_file, :item_xpath
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
# @option options [ Boolean ] :show_progression Whether or not output the progress bar
|
||||
# @option options [ Boolean ] :only_vulnerable Only check for vulnerable items
|
||||
# @option options [ String ] :exclude_content
|
||||
#
|
||||
# @return [ WpItems ]
|
||||
def aggressive_detection(wp_target, options = {})
|
||||
browser = Browser.instance
|
||||
hydra = browser.hydra
|
||||
targets = targets_items(wp_target, options)
|
||||
progress_bar = progress_bar(targets.size, options)
|
||||
queue_count = 0
|
||||
exist_options = {
|
||||
error_404_hash: wp_target.error_404_hash,
|
||||
homepage_hash: wp_target.homepage_hash,
|
||||
exclude_content: options[:exclude_content] ? %r{#{options[:exclude_content]}} : nil
|
||||
}
|
||||
|
||||
# If we only want the vulnerable ones, the passive detection is ignored
|
||||
# Otherwise, a passive detection is performed, and results will be merged
|
||||
results = options[:only_vulnerable] ? new : passive_detection(wp_target, options)
|
||||
|
||||
targets.each do |target_item|
|
||||
request = browser.forge_request(target_item.url, request_params)
|
||||
|
||||
request.on_complete do |response|
|
||||
progress_bar.progress += 1 if options[:show_progression]
|
||||
|
||||
if target_item.exists?(exist_options, response)
|
||||
if !results.include?(target_item)
|
||||
if !options[:only_vulnerable] || options[:only_vulnerable] && target_item.vulnerable?
|
||||
results << target_item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hydra.queue(request)
|
||||
queue_count += 1
|
||||
|
||||
if queue_count >= browser.max_threads
|
||||
hydra.run
|
||||
queue_count = 0
|
||||
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
|
||||
end
|
||||
end
|
||||
|
||||
# run the remaining requests
|
||||
hydra.run
|
||||
results.sort!
|
||||
results # can't just return results.sort because the #sort returns an array, and we want a WpItems
|
||||
end
|
||||
|
||||
# @param [ Integer ] targets_size
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ ProgressBar ]
|
||||
# :nocov:
|
||||
def progress_bar(targets_size, options)
|
||||
if options[:show_progression]
|
||||
ProgressBar.create(
|
||||
format: '%t %a <%B> (%c / %C) %P%% %e',
|
||||
title: ' ', # Used to craete a left margin
|
||||
total: targets_size
|
||||
)
|
||||
end
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ WpItems ]
|
||||
def passive_detection(wp_target, options = {})
|
||||
results = new(wp_target)
|
||||
# improves speed
|
||||
body = remove_base64_images_from_html(Browser.get(wp_target.url).body)
|
||||
page = Nokogiri::HTML(body)
|
||||
names = []
|
||||
|
||||
page.css('link,script,style').each do |tag|
|
||||
%w(href src).each do |attribute|
|
||||
attr_value = tag.attribute(attribute).to_s
|
||||
next unless attr_value
|
||||
|
||||
names << Regexp.last_match[1] if attr_value.match(attribute_pattern(wp_target))
|
||||
end
|
||||
|
||||
next unless tag.name == 'script' || tag.name == 'style'
|
||||
|
||||
code = tag.text.to_s
|
||||
next if code.empty?
|
||||
|
||||
code.scan(code_pattern(wp_target)).flatten.uniq.each do |item_name|
|
||||
names << item_name
|
||||
end
|
||||
end
|
||||
|
||||
names.uniq.each { |name| results.add(name) }
|
||||
|
||||
results.sort!
|
||||
results
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
#
|
||||
# @return [ Regex ]
|
||||
def item_pattern(wp_target)
|
||||
type = to_s.gsub(/Wp/, '').downcase
|
||||
wp_content_dir = wp_target.wp_content_dir
|
||||
wp_content_url = wp_target.uri.merge(wp_content_dir).to_s
|
||||
|
||||
url = /#{wp_content_url.gsub(%r{\A(?:http|https)}, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
content_dir = %r{(?:#{url}|\\?\/\\?\/?#{wp_content_dir})}i
|
||||
|
||||
%r{#{content_dir}\\?/#{type}\\?/}
|
||||
end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
#
|
||||
# @return [ Regex ]
|
||||
def attribute_pattern(wp_target)
|
||||
/\A#{item_pattern(wp_target)}([^\/]+)/i
|
||||
end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
#
|
||||
# @return [ Regex ]
|
||||
def code_pattern(wp_target)
|
||||
/["'\(]#{item_pattern(wp_target)}([^\\\/\)"']+)/i
|
||||
end
|
||||
|
||||
# The default request parameters
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def request_params; { cache_ttl: 0, followlocation: true } end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ options ] options
|
||||
# @option options [ Boolean ] :only_vulnerable
|
||||
# @option options [ String ] :file The path to the file containing the targets
|
||||
#
|
||||
# @return [ Array<WpItem> ]
|
||||
def targets_items(wp_target, options = {})
|
||||
item_class = self.item_class
|
||||
vulns_file = self.vulns_file
|
||||
|
||||
targets = vulnerable_targets_items(wp_target, item_class, vulns_file)
|
||||
|
||||
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.sort_by { rand }
|
||||
end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Class ] item_class
|
||||
# @param [ String ] vulns_file
|
||||
#
|
||||
# @return [ Array<WpItem> ]
|
||||
def vulnerable_targets_items(wp_target, item_class, vulns_file)
|
||||
targets = []
|
||||
json = json(vulns_file)
|
||||
|
||||
[*json].each do |item|
|
||||
targets << create_item(
|
||||
item_class,
|
||||
item.keys.inject,
|
||||
wp_target,
|
||||
vulns_file
|
||||
)
|
||||
end
|
||||
|
||||
targets
|
||||
end
|
||||
|
||||
# @param [ Class ] klass
|
||||
# @param [ String ] name
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @option [ String ] vulns_file
|
||||
#
|
||||
# @return [ WpItem ]
|
||||
def create_item(klass, name, wp_target, vulns_file = nil)
|
||||
klass.new(
|
||||
wp_target.uri,
|
||||
name: name,
|
||||
vulns_file: vulns_file,
|
||||
wp_content_dir: wp_target.wp_content_dir,
|
||||
wp_plugins_dir: wp_target.wp_plugins_dir
|
||||
)
|
||||
end
|
||||
|
||||
# @param [ String ] file
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Class ] item_class
|
||||
# @param [ String ] vulns_file
|
||||
#
|
||||
# @return [ Array<WpItem> ]
|
||||
def targets_items_from_file(file, wp_target, item_class, vulns_file)
|
||||
targets = []
|
||||
|
||||
File.open(file, 'r') do |f|
|
||||
f.readlines.collect do |item_name|
|
||||
targets << create_item(
|
||||
item_class,
|
||||
item_name.strip,
|
||||
wp_target,
|
||||
vulns_file
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
targets
|
||||
end
|
||||
|
||||
# @return [ Class ]
|
||||
def item_class
|
||||
Object.const_get(self.to_s.gsub(/.$/, ''))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItems < Array
|
||||
module Detectable
|
||||
|
||||
attr_reader :vulns_file, :item_xpath
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
# @option options [ Boolean ] :show_progression Whether or not output the progress bar
|
||||
# @option options [ Boolean ] :only_vulnerable Only check for vulnerable items
|
||||
# @option options [ String ] :exclude_content
|
||||
#
|
||||
# @return [ WpItems ]
|
||||
def aggressive_detection(wp_target, options = {})
|
||||
browser = Browser.instance
|
||||
hydra = browser.hydra
|
||||
targets = targets_items(wp_target, options)
|
||||
progress_bar = progress_bar(targets.size, options)
|
||||
queue_count = 0
|
||||
exist_options = {
|
||||
error_404_hash: wp_target.error_404_hash,
|
||||
homepage_hash: wp_target.homepage_hash,
|
||||
exclude_content: options[:exclude_content] ? %r{#{options[:exclude_content]}} : nil
|
||||
}
|
||||
results = passive_detection(wp_target, options)
|
||||
|
||||
targets.each do |target_item|
|
||||
request = browser.forge_request(target_item.url, request_params)
|
||||
|
||||
request.on_complete do |response|
|
||||
progress_bar.progress += 1 if options[:show_progression]
|
||||
|
||||
if target_item.exists?(exist_options, response)
|
||||
results << target_item unless results.include?(target_item)
|
||||
end
|
||||
end
|
||||
|
||||
hydra.queue(request)
|
||||
queue_count += 1
|
||||
|
||||
if queue_count >= browser.max_threads
|
||||
hydra.run
|
||||
queue_count = 0
|
||||
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
|
||||
end
|
||||
end
|
||||
|
||||
# run the remaining requests
|
||||
hydra.run
|
||||
|
||||
results.select!(&:vulnerable?) if options[:type] == :vulnerable
|
||||
results.sort!
|
||||
|
||||
results # can't just return results.sort as it would return an array, and we want a WpItems
|
||||
end
|
||||
|
||||
# @param [ Integer ] targets_size
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ ProgressBar ]
|
||||
# :nocov:
|
||||
def progress_bar(targets_size, options)
|
||||
if options[:show_progression]
|
||||
ProgressBar.create(
|
||||
format: '%t %a <%B> (%c / %C) %P%% %e',
|
||||
title: ' ', # Used to craete a left margin
|
||||
total: targets_size
|
||||
)
|
||||
end
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ WpItems ]
|
||||
def passive_detection(wp_target, options = {})
|
||||
results = new(wp_target)
|
||||
# improves speed
|
||||
body = remove_base64_images_from_html(Browser.get(wp_target.url).body)
|
||||
page = Nokogiri::HTML(body)
|
||||
names = []
|
||||
|
||||
page.css('link,script,style').each do |tag|
|
||||
%w(href src).each do |attribute|
|
||||
attr_value = tag.attribute(attribute).to_s
|
||||
next unless attr_value
|
||||
|
||||
names << Regexp.last_match[1] if attr_value.match(attribute_pattern(wp_target))
|
||||
end
|
||||
|
||||
next unless tag.name == 'script' || tag.name == 'style'
|
||||
|
||||
code = tag.text.to_s
|
||||
next if code.empty?
|
||||
|
||||
code.scan(code_pattern(wp_target)).flatten.uniq.each do |item_name|
|
||||
names << item_name
|
||||
end
|
||||
end
|
||||
|
||||
names.uniq.each { |name| results.add(name) }
|
||||
|
||||
results.sort!
|
||||
results
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
#
|
||||
# @return [ Regex ]
|
||||
def item_pattern(wp_target)
|
||||
type = to_s.gsub(/Wp/, '').downcase
|
||||
wp_content_dir = wp_target.wp_content_dir
|
||||
wp_content_url = wp_target.uri.merge(wp_content_dir).to_s
|
||||
|
||||
url = /#{wp_content_url.gsub(%r{\A(?:http|https)}, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
content_dir = %r{(?:#{url}|\\?\/\\?\/?#{wp_content_dir})}i
|
||||
|
||||
%r{#{content_dir}\\?/#{type}\\?/}
|
||||
end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
#
|
||||
# @return [ Regex ]
|
||||
def attribute_pattern(wp_target)
|
||||
/\A#{item_pattern(wp_target)}([^\/]+)/i
|
||||
end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
#
|
||||
# @return [ Regex ]
|
||||
def code_pattern(wp_target)
|
||||
/["'\(]#{item_pattern(wp_target)}([^\\\/\)"']+)/i
|
||||
end
|
||||
|
||||
# The default request parameters
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def request_params; { cache_ttl: 0, followlocation: true } end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ options ] options
|
||||
# @option options [ Boolean ] :only_vulnerable
|
||||
# @option options [ String ] :file The path to the file containing the targets
|
||||
#
|
||||
# @return [ Array<WpItem> ]
|
||||
def targets_items(wp_target, options = {})
|
||||
item_class = self.item_class
|
||||
vulns_file = self.vulns_file
|
||||
|
||||
targets = target_items_from_type(wp_target, item_class, vulns_file, options[:type])
|
||||
|
||||
targets.uniq! { |t| t.name }
|
||||
targets.sort_by { rand }
|
||||
end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Class ] item_class
|
||||
# @param [ String ] vulns_file
|
||||
#
|
||||
# @return [ Array<WpItem> ]
|
||||
def target_items_from_type(wp_target, item_class, vulns_file, type)
|
||||
targets = []
|
||||
json = json(vulns_file)
|
||||
|
||||
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(
|
||||
item_class,
|
||||
item,
|
||||
wp_target,
|
||||
vulns_file
|
||||
)
|
||||
end
|
||||
|
||||
targets
|
||||
end
|
||||
|
||||
# @param [ Class ] klass
|
||||
# @param [ String ] name
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @option [ String ] vulns_file
|
||||
#
|
||||
# @return [ WpItem ]
|
||||
def create_item(klass, name, wp_target, vulns_file = nil)
|
||||
klass.new(
|
||||
wp_target.uri,
|
||||
name: name,
|
||||
vulns_file: vulns_file,
|
||||
wp_content_dir: wp_target.wp_content_dir,
|
||||
wp_plugins_dir: wp_target.wp_plugins_dir
|
||||
)
|
||||
end
|
||||
|
||||
# @param [ String ] file
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Class ] item_class
|
||||
# @param [ String ] vulns_file
|
||||
#
|
||||
# @return [ Array<WpItem> ]
|
||||
def targets_items_from_file(file, wp_target, item_class, vulns_file)
|
||||
targets = []
|
||||
|
||||
File.open(file, 'r') do |f|
|
||||
f.readlines.collect do |item_name|
|
||||
targets << create_item(
|
||||
item_class,
|
||||
item_name.strip,
|
||||
wp_target,
|
||||
vulns_file
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
targets
|
||||
end
|
||||
|
||||
# @return [ Class ]
|
||||
def item_class
|
||||
Object.const_get(self.to_s.gsub(/.$/, ''))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_plugins/detectable'
|
||||
|
||||
class WpPlugins < WpItems
|
||||
extend WpPlugins::Detectable
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_plugins/detectable'
|
||||
|
||||
class WpPlugins < WpItems
|
||||
extend WpPlugins::Detectable
|
||||
|
||||
end
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
|
||||
class WpPlugins < WpItems
|
||||
module Detectable
|
||||
|
||||
# @return [ String ]
|
||||
def vulns_file
|
||||
PLUGINS_VULNS_FILE
|
||||
PLUGINS_FILE
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
# def item_xpath
|
||||
# '//plugin'
|
||||
# end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_themes/detectable'
|
||||
|
||||
class WpThemes < WpItems
|
||||
extend WpThemes::Detectable
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_themes/detectable'
|
||||
|
||||
class WpThemes < WpItems
|
||||
extend WpThemes::Detectable
|
||||
|
||||
end
|
||||
|
||||
@@ -5,13 +5,7 @@ class WpThemes < WpItems
|
||||
|
||||
# @return [ String ]
|
||||
def vulns_file
|
||||
THEMES_VULNS_FILE
|
||||
THEMES_FILE
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
# def item_xpath
|
||||
# '//theme'
|
||||
# end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_timthumbs/detectable'
|
||||
|
||||
class WpTimthumbs < WpItems
|
||||
extend WpTimthumbs::Detectable
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_timthumbs/detectable'
|
||||
|
||||
class WpTimthumbs < WpItems
|
||||
extend WpTimthumbs::Detectable
|
||||
|
||||
end
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_users/detectable'
|
||||
require 'common/collections/wp_users/output'
|
||||
require 'common/collections/wp_users/brute_forcable'
|
||||
|
||||
class WpUsers < WpItems
|
||||
extend WpUsers::Detectable
|
||||
include WpUsers::Output
|
||||
include WpUsers::BruteForcable
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_users/detectable'
|
||||
require 'common/collections/wp_users/output'
|
||||
require 'common/collections/wp_users/brute_forcable'
|
||||
|
||||
class WpUsers < WpItems
|
||||
extend WpUsers::Detectable
|
||||
include WpUsers::Output
|
||||
include WpUsers::BruteForcable
|
||||
end
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUsers < WpItems
|
||||
module Detectable
|
||||
|
||||
# @return [ Hash ]
|
||||
def request_params; {} end
|
||||
|
||||
# No passive detection
|
||||
#
|
||||
# @return [ WpUsers ]
|
||||
def passive_detection(wp_target, options = {})
|
||||
new
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
# @option options [ Range ] :range ((1..10))
|
||||
#
|
||||
# @return [ Array<WpUser> ]
|
||||
def targets_items(wp_target, options = {})
|
||||
range = options[:range] || (1..10)
|
||||
targets = []
|
||||
|
||||
range.each do |user_id|
|
||||
targets << WpUser.new(wp_target.uri, id: user_id)
|
||||
end
|
||||
targets
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUsers < WpItems
|
||||
module Detectable
|
||||
|
||||
# @return [ Hash ]
|
||||
def request_params; {} end
|
||||
|
||||
# No passive detection
|
||||
#
|
||||
# @return [ WpUsers ]
|
||||
def passive_detection(wp_target, options = {})
|
||||
new
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
# @option options [ Range ] :range ((1..10))
|
||||
#
|
||||
# @return [ Array<WpUser> ]
|
||||
def targets_items(wp_target, options = {})
|
||||
range = options[:range] || (1..10)
|
||||
targets = []
|
||||
|
||||
range.each do |user_id|
|
||||
targets << WpUser.new(wp_target.uri, id: user_id)
|
||||
end
|
||||
targets
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,6 +38,7 @@ class WpUsers < WpItems
|
||||
junk = get_equal_string_end(display_names)
|
||||
unless junk.nil? or junk.empty?
|
||||
self.each do |u|
|
||||
u.display_name ||= ''
|
||||
u.display_name = u.display_name.sub(/#{Regexp.escape(junk)}$/, '')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,6 @@ DATA_DIR = File.join(ROOT_DIR, 'data')
|
||||
CONF_DIR = File.join(ROOT_DIR, 'conf')
|
||||
CACHE_DIR = File.join(ROOT_DIR, 'cache')
|
||||
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan')
|
||||
WPSTOOLS_LIB_DIR = File.join(LIB_DIR, 'wpstools')
|
||||
UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater')
|
||||
COMMON_LIB_DIR = File.join(LIB_DIR, 'common')
|
||||
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models')
|
||||
@@ -17,24 +16,21 @@ LOG_FILE = File.join(ROOT_DIR, 'log.txt')
|
||||
# Plugins directories
|
||||
COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins')
|
||||
WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM
|
||||
WPSTOOLS_PLUGINS_DIR = File.join(WPSTOOLS_LIB_DIR, 'plugins')
|
||||
|
||||
# Data files
|
||||
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.txt')
|
||||
PLUGINS_FULL_FILE = File.join(DATA_DIR, 'plugins_full.txt')
|
||||
PLUGINS_VULNS_FILE = File.join(DATA_DIR, 'plugin_vulns.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')
|
||||
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')
|
||||
LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd')
|
||||
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
|
||||
WORDPRESSES_FILE = File.join(DATA_DIR, 'wordpresses.json')
|
||||
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.json')
|
||||
THEMES_FILE = File.join(DATA_DIR, 'themes.json')
|
||||
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml')
|
||||
LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml')
|
||||
WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd')
|
||||
LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd')
|
||||
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
|
||||
LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update')
|
||||
|
||||
WPSCAN_VERSION = '2.5.1'
|
||||
MIN_RUBY_VERSION = '2.1.9'
|
||||
|
||||
WPSCAN_VERSION = '2.9.1'
|
||||
|
||||
$LOAD_PATH.unshift(LIB_DIR)
|
||||
$LOAD_PATH.unshift(WPSCAN_LIB_DIR)
|
||||
@@ -42,22 +38,31 @@ $LOAD_PATH.unshift(MODELS_LIB_DIR)
|
||||
|
||||
def kali_linux?
|
||||
begin
|
||||
File.readlines("/etc/debian_version").grep(/^kali/i).any?
|
||||
File.readlines('/etc/debian_version').grep(/^kali/i).any?
|
||||
rescue
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Determins if installed on Windows OS
|
||||
def windows?
|
||||
Gem.win_platform?
|
||||
end
|
||||
|
||||
require 'environment'
|
||||
|
||||
def escape_glob(s)
|
||||
s.gsub(/[\\\{\}\[\]\*\?]/) { |x| '\\' + x }
|
||||
end
|
||||
|
||||
# TODO : add an exclude pattern ?
|
||||
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.sort_by { |file| [file.count("/"), file] }.each do |f|
|
||||
files.sort_by { |file| [file.count('/'), file] }.each do |f|
|
||||
f = File.expand_path(f)
|
||||
#puts "require #{f}" # Used for debug
|
||||
# puts "require #{f}" # Used for debug
|
||||
require f
|
||||
end
|
||||
end
|
||||
@@ -80,6 +85,20 @@ def missing_db_file?
|
||||
false
|
||||
end
|
||||
|
||||
def last_update
|
||||
date = nil
|
||||
if File.exists?(LAST_UPDATE_FILE)
|
||||
content = File.read(LAST_UPDATE_FILE)
|
||||
date = Time.parse(content) rescue nil
|
||||
end
|
||||
date
|
||||
end
|
||||
|
||||
def update_required?
|
||||
date = last_update
|
||||
(true if date.nil?) or (date < 5.days.ago)
|
||||
end
|
||||
|
||||
# Define colors
|
||||
def colorize(text, color_code)
|
||||
if $COLORSWITCH
|
||||
@@ -110,19 +129,21 @@ def blue(text)
|
||||
end
|
||||
|
||||
def critical(text)
|
||||
red(text)
|
||||
$exit_code += 1 if defined?($exit_code) # hack for undefined var via rspec
|
||||
"#{red('[!]')} #{text}"
|
||||
end
|
||||
|
||||
def warning(text)
|
||||
amber(text)
|
||||
$exit_code += 1 if defined?($exit_code) # hack for undefined var via rspec
|
||||
"#{amber('[!]')} #{text}"
|
||||
end
|
||||
|
||||
def info(text)
|
||||
green(text)
|
||||
"#{green('[+]')} #{text}"
|
||||
end
|
||||
|
||||
def notice(text)
|
||||
blue(text)
|
||||
"#{blue('[i]')} #{text}"
|
||||
end
|
||||
|
||||
# our 1337 banner
|
||||
@@ -137,7 +158,7 @@ def banner
|
||||
puts
|
||||
puts ' WordPress Security Scanner by the WPScan Team '
|
||||
puts " Version #{WPSCAN_VERSION}"
|
||||
puts ' Sponsored by the RandomStorm Open Source Initiative'
|
||||
puts ' Sponsored by Sucuri - https://sucuri.net'
|
||||
puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, pvdl, @_FireFart_'
|
||||
puts '_______________________________________________________________'
|
||||
puts
|
||||
@@ -209,7 +230,11 @@ end
|
||||
#
|
||||
# @return [ Integer ] The number of lines in the given file
|
||||
def count_file_lines(file)
|
||||
`wc -l #{file.shellescape}`.split[0].to_i
|
||||
if windows?
|
||||
`findstr /R /N "^" #{file.shellescape} | find /C ":"`.split[0].to_i
|
||||
else
|
||||
`wc -l #{file.shellescape}`.split[0].to_i
|
||||
end
|
||||
end
|
||||
|
||||
# Truncates a string to a specific length and adds ... at the end
|
||||
@@ -243,3 +268,7 @@ end
|
||||
def directory_listing_enabled?(url)
|
||||
Browser.get(url.to_s).body[%r{<title>Index of}] ? true : false
|
||||
end
|
||||
|
||||
def url_encode(str)
|
||||
CGI.escape(str).gsub("+", "%20")
|
||||
end
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
# DB Updater
|
||||
class DbUpdater
|
||||
FILES = %w(
|
||||
local_vulnerable_files.xml local_vulnerable_files.xsd malwares.txt
|
||||
plugins_full.txt plugins.txt themes_full.txt themes.txt
|
||||
local_vulnerable_files.xml local_vulnerable_files.xsd
|
||||
timthumbs.txt user-agents.txt wp_versions.xml wp_versions.xsd
|
||||
plugin_vulns.json theme_vulns.json wp_vulns.json
|
||||
wordpresses.json plugins.json themes.json LICENSE
|
||||
)
|
||||
|
||||
attr_reader :repo_directory
|
||||
@@ -22,13 +21,16 @@ class DbUpdater
|
||||
def request_params
|
||||
{
|
||||
ssl_verifyhost: 2,
|
||||
ssl_verifypeer: true
|
||||
ssl_verifypeer: true,
|
||||
accept_encoding: 'gzip, deflate',
|
||||
timeout: 300,
|
||||
connecttimeout: 20
|
||||
}
|
||||
end
|
||||
|
||||
# @return [ String ] The raw file URL associated with the given filename
|
||||
def remote_file_url(filename)
|
||||
"https://raw.githubusercontent.com/wpscanteam/vulndb/master/#{filename}"
|
||||
"https://data.wpscan.org/#{filename}"
|
||||
end
|
||||
|
||||
# @return [ String ] The checksum of the associated remote filename
|
||||
@@ -36,8 +38,8 @@ class DbUpdater
|
||||
url = "#{remote_file_url(filename)}.sha512"
|
||||
|
||||
res = Browser.get(url, request_params)
|
||||
fail "Unable to get #{url}" unless res.code == 200
|
||||
res.body
|
||||
fail DownloadError, res if res.timed_out? || res.code != 200
|
||||
res.body.chomp
|
||||
end
|
||||
|
||||
def local_file_path(filename)
|
||||
@@ -72,8 +74,8 @@ class DbUpdater
|
||||
file_url = remote_file_url(filename)
|
||||
|
||||
res = Browser.get(file_url, request_params)
|
||||
fail "Error while downloading #{file_url}" unless res.code == 200
|
||||
File.write(file_path, res.body)
|
||||
fail DownloadError, res if res.timed_out? || res.code != 200
|
||||
File.open(file_path, 'wb') { |f| f.write(res.body) }
|
||||
|
||||
local_file_checksum(filename)
|
||||
end
|
||||
@@ -96,9 +98,10 @@ class DbUpdater
|
||||
puts ' [i] Downloading new file' if verbose
|
||||
dl_checksum = download(filename)
|
||||
puts " [i] Downloaded File Checksum: #{dl_checksum}" if verbose
|
||||
puts " [i] Database File Checksum : #{db_checksum}" if verbose
|
||||
|
||||
unless dl_checksum == db_checksum
|
||||
fail "#{filename}: checksums do not match"
|
||||
fail "#{filename}: checksums do not match (local: #{dl_checksum} remote: #{db_checksum})"
|
||||
end
|
||||
rescue => e
|
||||
puts ' [i] Restoring Backup due to error' if verbose
|
||||
@@ -111,5 +114,8 @@ class DbUpdater
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# write last_update date to file
|
||||
File.write(LAST_UPDATE_FILE, Time.now)
|
||||
end
|
||||
end
|
||||
|
||||
33
lib/common/errors.rb
Normal file
33
lib/common/errors.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
# HTTP Error
|
||||
class HttpError < StandardError
|
||||
attr_reader :response
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
def initialize(response)
|
||||
@response = response
|
||||
end
|
||||
|
||||
def failure_details
|
||||
msg = response.effective_url
|
||||
|
||||
if response.code == 0 || response.timed_out?
|
||||
msg += " (#{response.return_message})"
|
||||
else
|
||||
msg += " (status: #{response.code})"
|
||||
end
|
||||
|
||||
msg
|
||||
end
|
||||
|
||||
def message
|
||||
"HTTP Error: #{failure_details}"
|
||||
end
|
||||
end
|
||||
|
||||
# Used in the Updater
|
||||
class DownloadError < HttpError
|
||||
def message
|
||||
"Unable to get #{failure_details}"
|
||||
end
|
||||
end
|
||||
@@ -1,35 +1,5 @@
|
||||
# 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
|
||||
module Typhoeus
|
||||
class Response
|
||||
@@ -49,11 +19,11 @@ end
|
||||
|
||||
# Override for puts to enable logging
|
||||
def puts(o = '')
|
||||
# remove color for logging
|
||||
if o.respond_to?(:gsub)
|
||||
temp = o.gsub(/\e\[\d+m/, '')
|
||||
if $log && o.respond_to?(:gsub)
|
||||
temp = o.gsub(/\e\[\d+m/, '') # remove color for logging
|
||||
File.open(LOG_FILE, 'a+') { |f| f.puts(temp) }
|
||||
end
|
||||
|
||||
super(o)
|
||||
end
|
||||
|
||||
@@ -78,7 +48,7 @@ module Terminal
|
||||
|
||||
class Style
|
||||
@@defaults = {
|
||||
:border_x => "-", :border_y => "|", :border_i => "+",
|
||||
:border_x => '-', :border_y => '|', :border_i => '+',
|
||||
:padding_left => 1, :padding_right => 1,
|
||||
:margin_left => '',
|
||||
:width => nil, :alignment => nil
|
||||
@@ -101,8 +71,21 @@ end
|
||||
class Numeric
|
||||
def bytes_to_human
|
||||
units = %w{B KB MB GB TB}
|
||||
e = (Math.log(self)/Math.log(1024)).floor
|
||||
s = "%.3f" % (to_f / 1024**e)
|
||||
e = (Math.log(abs)/Math.log(1024)).floor
|
||||
s = '%.3f' % (abs.to_f / 1024**e)
|
||||
s.sub(/\.?0*$/, ' ' + units[e])
|
||||
end
|
||||
end
|
||||
|
||||
# time calculations
|
||||
class Fixnum
|
||||
SECONDS_IN_DAY = 24 * 60 * 60
|
||||
|
||||
def days
|
||||
self * SECONDS_IN_DAY
|
||||
end
|
||||
|
||||
def ago
|
||||
Time.now - self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,61 +1,62 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'vulnerability/output'
|
||||
require 'vulnerability/urls'
|
||||
|
||||
class Vulnerability
|
||||
include Vulnerability::Output
|
||||
include Vulnerability::Urls
|
||||
|
||||
attr_accessor :title, :references, :type, :fixed_in
|
||||
|
||||
#
|
||||
# @param [ String ] title The title of the vulnerability
|
||||
# @param [ String ] type The type of the vulnerability
|
||||
# @param [ Hash ] references References
|
||||
# @param [ String ] fixed_in Vuln fixed in Version X
|
||||
#
|
||||
# @return [ Vulnerability ]
|
||||
def initialize(title, type, references = {}, fixed_in = '')
|
||||
@title = title
|
||||
@type = type
|
||||
@references = references
|
||||
@fixed_in = fixed_in
|
||||
end
|
||||
|
||||
# @param [ Vulnerability ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
# :nocov:
|
||||
def ==(other)
|
||||
title == other.title &&
|
||||
type == other.type &&
|
||||
references == other.references &&
|
||||
fixed_in == other.fixed_in
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# Create the Vulnerability from the json_item
|
||||
#
|
||||
# @param [ Hash ] json_item
|
||||
#
|
||||
# @return [ Vulnerability ]
|
||||
def self.load_from_json_item(json_item)
|
||||
references = {}
|
||||
|
||||
%w(id url cve secunia osvdb metasploit exploitdb).each do |key|
|
||||
if json_item[key]
|
||||
json_item[key] = [json_item[key]] if json_item[key].class != Array
|
||||
references[key] = json_item[key]
|
||||
end
|
||||
end
|
||||
|
||||
new(
|
||||
json_item['title'],
|
||||
json_item['type'],
|
||||
references,
|
||||
json_item['fixed_in'],
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'vulnerability/output'
|
||||
require 'vulnerability/urls'
|
||||
|
||||
class Vulnerability
|
||||
include Vulnerability::Output
|
||||
include Vulnerability::Urls
|
||||
|
||||
attr_accessor :title, :references, :type, :fixed_in
|
||||
|
||||
#
|
||||
# @param [ String ] title The title of the vulnerability
|
||||
# @param [ String ] type The type of the vulnerability
|
||||
# @param [ Hash ] references References
|
||||
# @param [ String ] fixed_in Vuln fixed in Version X
|
||||
#
|
||||
# @return [ Vulnerability ]
|
||||
def initialize(title, type, references = {}, fixed_in = '')
|
||||
@title = title
|
||||
@type = type
|
||||
@references = references
|
||||
@fixed_in = fixed_in
|
||||
end
|
||||
|
||||
# @param [ Vulnerability ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
# :nocov:
|
||||
def ==(other)
|
||||
title == other.title &&
|
||||
type == other.type &&
|
||||
references == other.references &&
|
||||
fixed_in == other.fixed_in
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# Create the Vulnerability from the json_item
|
||||
#
|
||||
# @param [ Hash ] json_item
|
||||
#
|
||||
# @return [ Vulnerability ]
|
||||
def self.load_from_json_item(json_item)
|
||||
references = {}
|
||||
references['id'] = [json_item['id']]
|
||||
|
||||
%w(url cve secunia osvdb metasploit exploitdb).each do |key|
|
||||
if json_item['references'][key]
|
||||
json_item['references'][key] = [json_item['references'][key]] if json_item['references'][key].class != Array
|
||||
references[key] = json_item['references'][key]
|
||||
end
|
||||
end
|
||||
|
||||
new(
|
||||
json_item['title'],
|
||||
json_item['type'],
|
||||
references,
|
||||
json_item['fixed_in']
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -2,21 +2,22 @@
|
||||
|
||||
class Vulnerability
|
||||
module Output
|
||||
|
||||
# output the vulnerability
|
||||
def output(verbose = false)
|
||||
puts
|
||||
puts "#{critical('[!]')} Title: #{title}"
|
||||
puts critical("Title: #{title}")
|
||||
|
||||
references.each do |key, urls|
|
||||
methodname = "url_#{key}"
|
||||
|
||||
urls.each do |u|
|
||||
next unless respond_to?(methodname)
|
||||
url = send(methodname, u)
|
||||
puts " Reference: #{url}" if url
|
||||
end
|
||||
end
|
||||
if !fixed_in.nil?
|
||||
puts "#{notice('[i]')} Fixed in: #{fixed_in}"
|
||||
end
|
||||
|
||||
puts notice("Fixed in: #{fixed_in}") if fixed_in
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,31 +6,39 @@ class Vulnerability
|
||||
def url_metasploit(module_path)
|
||||
# remove leading slash
|
||||
module_path = module_path.sub(/^\//, '')
|
||||
"http://www.rapid7.com/db/modules/#{module_path}"
|
||||
"https://www.rapid7.com/db/modules/#{module_path}"
|
||||
end
|
||||
|
||||
def url_url(url)
|
||||
url
|
||||
end
|
||||
|
||||
def url_cve(cve)
|
||||
"http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-#{cve}"
|
||||
def url_cve(id)
|
||||
"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-#{id}"
|
||||
end
|
||||
|
||||
def url_osvdb(id)
|
||||
"http://osvdb.org/#{id}"
|
||||
"http://osvdb.org/show/osvdb/#{id}"
|
||||
end
|
||||
|
||||
def url_secunia(id)
|
||||
"https://secunia.com/advisories/#{id}"
|
||||
"https://secunia.com/advisories/#{id}/"
|
||||
end
|
||||
|
||||
def url_exploitdb(id)
|
||||
"http://www.exploit-db.com/exploits/#{id}/"
|
||||
"https://www.exploit-db.com/exploits/#{id}/"
|
||||
end
|
||||
|
||||
def url_id(id)
|
||||
"https://wpvulndb.com/vulnerabilities/#{id}"
|
||||
end
|
||||
|
||||
def url_packetstorm(id)
|
||||
"http://packetstormsecurity.com/files/#{id}/"
|
||||
end
|
||||
|
||||
def url_securityfocus(id)
|
||||
"http://www.securityfocus.com/bid/#{id}/"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,103 +1,121 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_item/findable'
|
||||
require 'wp_item/versionable'
|
||||
require 'wp_item/vulnerable'
|
||||
require 'wp_item/existable'
|
||||
require 'wp_item/infos'
|
||||
require 'wp_item/output'
|
||||
|
||||
class WpItem
|
||||
|
||||
extend WpItem::Findable
|
||||
include WpItem::Versionable
|
||||
include WpItem::Vulnerable
|
||||
include WpItem::Existable
|
||||
include WpItem::Infos
|
||||
include WpItem::Output
|
||||
|
||||
attr_reader :path
|
||||
attr_accessor :name, :wp_content_dir, :wp_plugins_dir
|
||||
|
||||
# @return [ Array ]
|
||||
# Make it private ?
|
||||
def allowed_options
|
||||
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :vulns_file]
|
||||
end
|
||||
|
||||
# @param [ URI ] target_base_uri
|
||||
# @param [ Hash ] options See allowed_option
|
||||
#
|
||||
# @return [ WpItem ]
|
||||
def initialize(target_base_uri, options = {})
|
||||
|
||||
options[:wp_content_dir] ||= 'wp-content'
|
||||
options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins'
|
||||
|
||||
set_options(options)
|
||||
forge_uri(target_base_uri)
|
||||
end
|
||||
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ void ]
|
||||
def set_options(options)
|
||||
allowed_options.each do |allowed_option|
|
||||
if options.has_key?(allowed_option)
|
||||
method = :"#{allowed_option}="
|
||||
|
||||
if self.respond_to?(method)
|
||||
self.send(method, options[allowed_option])
|
||||
else
|
||||
raise "#{self.class} does not respond to #{method}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
private :set_options
|
||||
|
||||
# @param [ URI ] target_base_uri
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri
|
||||
end
|
||||
|
||||
# @return [ URI ] The uri to the WpItem, with the path if present
|
||||
def uri
|
||||
path ? @uri.merge(path) : @uri
|
||||
end
|
||||
|
||||
# @return [ String ] The url to the WpItem
|
||||
def url; uri.to_s end
|
||||
|
||||
# Sets the path
|
||||
#
|
||||
# Variable, such as $wp-plugins$ and $wp-content$ can be used
|
||||
# and will be replace by their value
|
||||
#
|
||||
# @param [ String ] path
|
||||
#
|
||||
# @return [ void ]
|
||||
def path=(path)
|
||||
@path = URI.encode(
|
||||
path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
|
||||
)
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def <=>(other)
|
||||
name <=> other.name
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def ==(other)
|
||||
name === other.name
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def ===(other)
|
||||
self == other && version === other.version
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_item/findable'
|
||||
require 'wp_item/versionable'
|
||||
require 'wp_item/vulnerable'
|
||||
require 'wp_item/existable'
|
||||
require 'wp_item/infos'
|
||||
require 'wp_item/output'
|
||||
|
||||
class WpItem
|
||||
|
||||
extend WpItem::Findable
|
||||
include WpItem::Versionable
|
||||
include WpItem::Vulnerable
|
||||
include WpItem::Existable
|
||||
include WpItem::Infos
|
||||
include WpItem::Output
|
||||
|
||||
attr_reader :path
|
||||
attr_accessor :name, :wp_content_dir, :wp_plugins_dir
|
||||
|
||||
# @return [ Array ]
|
||||
# Make it private ?
|
||||
def allowed_options
|
||||
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :db_file]
|
||||
end
|
||||
|
||||
# @param [ URI ] target_base_uri
|
||||
# @param [ Hash ] options See allowed_option
|
||||
#
|
||||
# @return [ WpItem ]
|
||||
def initialize(target_base_uri, options = {})
|
||||
options[:wp_content_dir] ||= 'wp-content'
|
||||
options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins'
|
||||
|
||||
set_options(options)
|
||||
forge_uri(target_base_uri)
|
||||
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
|
||||
#
|
||||
# @return [ void ]
|
||||
def set_options(options)
|
||||
allowed_options.each do |allowed_option|
|
||||
if options.has_key?(allowed_option)
|
||||
method = :"#{allowed_option}="
|
||||
|
||||
if self.respond_to?(method)
|
||||
self.send(method, options[allowed_option])
|
||||
else
|
||||
raise "#{self.class} does not respond to #{method}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
private :set_options
|
||||
|
||||
# @param [ URI ] target_base_uri
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri
|
||||
end
|
||||
|
||||
# @return [ URI ] The uri to the WpItem, with the path if present
|
||||
def uri
|
||||
path ? @uri.merge(path) : @uri
|
||||
end
|
||||
|
||||
# @return [ String ] The url to the WpItem
|
||||
def url; uri.to_s end
|
||||
|
||||
# Sets the path
|
||||
#
|
||||
# Variable, such as $wp-plugins$ and $wp-content$ can be used
|
||||
# and will be replace by their value
|
||||
#
|
||||
# @param [ String ] path
|
||||
#
|
||||
# @return [ void ]
|
||||
def path=(path)
|
||||
@path = path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def <=>(other)
|
||||
name <=> other.name
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def ==(other)
|
||||
name === other.name
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def ===(other)
|
||||
self == other && version === other.version
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
module Existable
|
||||
|
||||
# Check the existence of the WpItem
|
||||
# If the response is supplied, it's used for the verification
|
||||
# Otherwise a new request is done
|
||||
#
|
||||
# @param [ Hash ] options See exists_from_response?
|
||||
# @param [ Typhoeus::Response ] response
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists?(options = {}, response = nil)
|
||||
unless response
|
||||
response = Browser.get(url)
|
||||
end
|
||||
exists_from_response?(response, options)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ options ] options
|
||||
#
|
||||
# @option options [ Hash ] :error_404_hash The hash of the error 404 page
|
||||
# @option options [ Hash ] :homepage_hash The hash of the homepage
|
||||
# @option options [ Hash ] :exclude_content A regexp with the pattern to exclude from the body of the response
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists_from_response?(response, options = {})
|
||||
# 301 included as some items do a self-redirect
|
||||
# Redirects to the 404 and homepage should be ignored (unless dynamic content is used)
|
||||
# by the page hashes (error_404_hash & homepage_hash)
|
||||
if [200, 401, 403, 301].include?(response.code)
|
||||
if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash])
|
||||
if options[:exclude_content]
|
||||
unless response.body.match(options[:exclude_content])
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
module Existable
|
||||
|
||||
# Check the existence of the WpItem
|
||||
# If the response is supplied, it's used for the verification
|
||||
# Otherwise a new request is done
|
||||
#
|
||||
# @param [ Hash ] options See exists_from_response?
|
||||
# @param [ Typhoeus::Response ] response
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists?(options = {}, response = nil)
|
||||
unless response
|
||||
response = Browser.get(url)
|
||||
end
|
||||
exists_from_response?(response, options)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ options ] options
|
||||
#
|
||||
# @option options [ Hash ] :error_404_hash The hash of the error 404 page
|
||||
# @option options [ Hash ] :homepage_hash The hash of the homepage
|
||||
# @option options [ Hash ] :exclude_content A regexp with the pattern to exclude from the body of the response
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists_from_response?(response, options = {})
|
||||
# 301 included as some items do a self-redirect
|
||||
# Redirects to the 404 and homepage should be ignored (unless dynamic content is used)
|
||||
# by the page hashes (error_404_hash & homepage_hash)
|
||||
if [200, 401, 403, 301].include?(response.code)
|
||||
if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash])
|
||||
if options[:exclude_content]
|
||||
unless response.body.match(options[:exclude_content])
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
attr_reader :found_from
|
||||
|
||||
# Sets the found_from attribute
|
||||
#
|
||||
# @param [ String ] method The method which found the WpItem
|
||||
#
|
||||
# @return [ void ]
|
||||
def found_from=(method)
|
||||
found = method[%r{find_from_(.*)}, 1]
|
||||
@found_from = found.gsub('_', ' ') if found
|
||||
end
|
||||
|
||||
module Findable
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
attr_reader :found_from
|
||||
|
||||
# Sets the found_from attribute
|
||||
#
|
||||
# @param [ String ] method The method which found the WpItem
|
||||
#
|
||||
# @return [ void ]
|
||||
def found_from=(method)
|
||||
found = method[%r{find_from_(.*)}, 1]
|
||||
@found_from = found.gsub('_', ' ') if found
|
||||
end
|
||||
|
||||
module Findable
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,7 +12,9 @@ class WpItem
|
||||
|
||||
# @return [ String,nil ] The url to the readme file, nil if not found
|
||||
def readme_url
|
||||
%w{readme.txt README.txt}.each do |readme|
|
||||
# See https://github.com/wpscanteam/wpscan/pull/737#issuecomment-66375445
|
||||
# for any question about the order
|
||||
%w{readme.txt README.txt Readme.txt ReadMe.txt README.TXT readme.TXT}.each do |readme|
|
||||
url = @uri.merge(readme).to_s
|
||||
return url if url_is_200?(url)
|
||||
end
|
||||
|
||||
@@ -5,20 +5,25 @@ class WpItem
|
||||
|
||||
# @return [ Void ]
|
||||
def output(verbose = false)
|
||||
outdated = VersionCompare.lesser?(version, latest_version) if latest_version
|
||||
|
||||
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 " | WordPress: #{wordpress_url}" if wordpress_org_item?
|
||||
puts " | Readme: #{readme_url}" if has_readme?
|
||||
puts " | Changelog: #{changelog_url}" if has_changelog?
|
||||
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("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("An error_log file has been found: #{error_log_url}") if has_error_log?
|
||||
|
||||
additional_output(verbose) if respond_to?(:additional_output)
|
||||
|
||||
if version.nil? && vulnerabilities.length > 0
|
||||
puts
|
||||
puts "#{warning('[+]')} We could not determine a version so all vulnerabilities are printed out"
|
||||
puts warning('We could not determine a version so all vulnerabilities are printed out')
|
||||
end
|
||||
|
||||
vulnerabilities.output
|
||||
|
||||
@@ -1,29 +1,53 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
attr_writer :version
|
||||
|
||||
module Versionable
|
||||
|
||||
# Get the version from the readme.txt
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def version
|
||||
unless @version
|
||||
# This check is needed because readme_url can return nil
|
||||
if has_readme?
|
||||
response = Browser.get(readme_url)
|
||||
@version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1]
|
||||
end
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
item_version = self.version
|
||||
"#@name#{' - v' + item_version.strip if item_version}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
attr_writer :version
|
||||
|
||||
module Versionable
|
||||
|
||||
# Get the version from the readme.txt
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def version
|
||||
unless @version
|
||||
# This check is needed because readme_url can return nil
|
||||
if has_readme?
|
||||
response = Browser.get(readme_url)
|
||||
@version = extract_version(response.body)
|
||||
end
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
item_version = self.version
|
||||
"#{@name}#{' - v' + item_version.strip if item_version}"
|
||||
end
|
||||
|
||||
# Extracts the version number from a given string/body
|
||||
#
|
||||
# @return [ String ] detected version
|
||||
def extract_version(body)
|
||||
version = body[/\b(?:stable tag|version):\s*(?!trunk)([0-9a-z\.-]+)/i, 1]
|
||||
if version.nil? || version !~ /[0-9]+/
|
||||
extracted_versions = body.scan(/[=]+\s+(?:v(?:ersion)?\s*)?([0-9\.-]+)[ \ta-z0-9\(\)\.-]*[=]+/i)
|
||||
return if extracted_versions.nil? || extracted_versions.length == 0
|
||||
extracted_versions.flatten!
|
||||
# must contain at least one number
|
||||
extracted_versions = extracted_versions.select { |x| x =~ /[0-9]+/ }
|
||||
sorted = extracted_versions.sort { |x,y|
|
||||
begin
|
||||
Gem::Version.new(x) <=> Gem::Version.new(y)
|
||||
rescue
|
||||
0
|
||||
end
|
||||
}
|
||||
return sorted.last
|
||||
else
|
||||
return version
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,49 +1,44 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
module Vulnerable
|
||||
attr_accessor :vulns_file, :identifier
|
||||
|
||||
# Get the vulnerabilities associated to the WpItem
|
||||
# Filters out already fixed vulnerabilities
|
||||
#
|
||||
# @return [ Vulnerabilities ]
|
||||
def vulnerabilities
|
||||
json = json(vulns_file)
|
||||
vulnerabilities = Vulnerabilities.new
|
||||
|
||||
json.each do |item|
|
||||
asset = item[identifier]
|
||||
|
||||
if asset
|
||||
asset['vulnerabilities'].each do |vulnerability|
|
||||
vulnerability = Vulnerability.load_from_json_item(vulnerability)
|
||||
vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vulnerabilities
|
||||
end
|
||||
|
||||
def vulnerable?
|
||||
vulnerabilities.empty? ? false : true
|
||||
end
|
||||
|
||||
# Checks if a item is vulnerable to a specific vulnerability
|
||||
#
|
||||
# @param [ Vulnerability ] vuln Vulnerability to check the item against
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def vulnerable_to?(vuln)
|
||||
if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
|
||||
unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version)
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
module Vulnerable
|
||||
attr_accessor :db_file, :identifier
|
||||
|
||||
# Get the vulnerabilities associated to the WpItem
|
||||
# Filters out already fixed vulnerabilities
|
||||
#
|
||||
# @return [ Vulnerabilities ]
|
||||
def vulnerabilities
|
||||
return @vulnerabilities if @vulnerabilities
|
||||
|
||||
@vulnerabilities = Vulnerabilities.new
|
||||
|
||||
[*db_data['vulnerabilities']].each do |vulnerability|
|
||||
vulnerability = Vulnerability.load_from_json_item(vulnerability)
|
||||
@vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
||||
end
|
||||
|
||||
@vulnerabilities
|
||||
end
|
||||
|
||||
def vulnerable?
|
||||
vulnerabilities.empty? ? false : true
|
||||
end
|
||||
|
||||
# Checks if a item is vulnerable to a specific vulnerability
|
||||
#
|
||||
# @param [ Vulnerability ] vuln Vulnerability to check the item against
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def vulnerable_to?(vuln)
|
||||
if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
|
||||
unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version)
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_plugin/vulnerable'
|
||||
|
||||
class WpPlugin < WpItem
|
||||
include WpPlugin::Vulnerable
|
||||
|
||||
# Sets the @uri
|
||||
#
|
||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri.merge(URI.encode(wp_plugins_dir + '/' + name + '/'))
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpPlugin < WpItem
|
||||
# Sets the @uri
|
||||
#
|
||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri.merge("#{wp_plugins_dir}/#{url_encode(name)}/")
|
||||
end
|
||||
|
||||
def db_file
|
||||
@db_file ||= PLUGINS_FILE
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -1,36 +1,37 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_theme/findable'
|
||||
require 'wp_theme/versionable'
|
||||
require 'wp_theme/vulnerable'
|
||||
require 'wp_theme/info'
|
||||
require 'wp_theme/output'
|
||||
require 'wp_theme/childtheme'
|
||||
|
||||
class WpTheme < WpItem
|
||||
extend WpTheme::Findable
|
||||
include WpTheme::Versionable
|
||||
include WpTheme::Vulnerable
|
||||
include WpTheme::Info
|
||||
include WpTheme::Output
|
||||
include WpTheme::Childtheme
|
||||
|
||||
attr_accessor :referenced_url
|
||||
|
||||
def allowed_options; super << :referenced_url end
|
||||
|
||||
# Sets the @uri
|
||||
#
|
||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri.merge(URI.encode(wp_content_dir + '/themes/' + name + '/'))
|
||||
end
|
||||
|
||||
# @return [ String ] The url to the theme stylesheet
|
||||
def style_url
|
||||
@uri.merge('style.css').to_s
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_theme/findable'
|
||||
require 'wp_theme/versionable'
|
||||
require 'wp_theme/info'
|
||||
require 'wp_theme/output'
|
||||
require 'wp_theme/childtheme'
|
||||
|
||||
class WpTheme < WpItem
|
||||
extend WpTheme::Findable
|
||||
include WpTheme::Versionable
|
||||
include WpTheme::Info
|
||||
include WpTheme::Output
|
||||
include WpTheme::Childtheme
|
||||
|
||||
attr_accessor :referenced_url
|
||||
|
||||
def allowed_options; super << :referenced_url end
|
||||
|
||||
# Sets the @uri
|
||||
#
|
||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri.merge("#{wp_content_dir}/themes/#{url_encode(name)}/")
|
||||
end
|
||||
|
||||
# @return [ String ] The url to the theme stylesheet
|
||||
def style_url
|
||||
@uri.merge('style.css').to_s
|
||||
end
|
||||
|
||||
def db_file
|
||||
@db_file ||= THEMES_FILE
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
class WpTheme < WpItem
|
||||
module Childtheme
|
||||
|
||||
def parent_theme_limit
|
||||
3
|
||||
end
|
||||
|
||||
def is_child_theme?
|
||||
return true unless @theme_template.nil?
|
||||
false
|
||||
@@ -10,7 +14,7 @@ class WpTheme < WpItem
|
||||
|
||||
def get_parent_theme_style_url
|
||||
if is_child_theme?
|
||||
return style_url.sub("/#{name}/style.css", "/#@theme_template/style.css")
|
||||
return style_url.sub("/#{name}/style.css", "/#{@theme_template}/style.css")
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -1,70 +1,64 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Findable
|
||||
|
||||
# Find the main theme of the blog
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find(target_uri)
|
||||
methods.grep(/^find_from_/).each do |method|
|
||||
if wp_theme = self.send(method, target_uri)
|
||||
wp_theme.found_from = method
|
||||
|
||||
return wp_theme
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Discover the wordpress theme by parsing the css link rel
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find_from_css_link(target_uri)
|
||||
response = Browser.get_and_follow_location(target_uri.to_s)
|
||||
|
||||
# https + domain is optional because of relative links
|
||||
matches = /(?:https?:\/\/[^"']+)?\/([^\/]+)\/themes\/([^"'\/]+)[^"']*\/style.css/i.match(response.body)
|
||||
if matches
|
||||
return new(
|
||||
target_uri,
|
||||
{
|
||||
name: matches[2],
|
||||
referenced_url: matches[0],
|
||||
wp_content_dir: matches[1]
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find_from_wooframework(target_uri)
|
||||
body = Browser.get(target_uri.to_s).body
|
||||
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
|
||||
|
||||
|
||||
if matches = regexp.match(body)
|
||||
woo_theme_name = matches[1]
|
||||
woo_theme_version = matches[2]
|
||||
#woo_framework_version = matches[3] # Not used at this time
|
||||
|
||||
return new(
|
||||
target_uri,
|
||||
{
|
||||
name: woo_theme_name,
|
||||
version: woo_theme_version
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Findable
|
||||
|
||||
# Find the main theme of the blog
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find(target_uri)
|
||||
methods.grep(/^find_from_/).each do |method|
|
||||
if wp_theme = self.send(method, target_uri)
|
||||
wp_theme.found_from = method
|
||||
|
||||
return wp_theme
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Discover the wordpress theme by parsing the css link rel
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find_from_css_link(target_uri)
|
||||
response = Browser.get_and_follow_location(target_uri.to_s)
|
||||
|
||||
# https + domain is optional because of relative links
|
||||
return unless response.body =~ %r{(?:https?://[^"']+/)?([^/\s]+)/themes/([^"'/]+)[^"']*/style.css}i
|
||||
|
||||
new(
|
||||
target_uri,
|
||||
name: Regexp.last_match[2],
|
||||
referenced_url: Regexp.last_match[0],
|
||||
wp_content_dir: Regexp.last_match[1]
|
||||
)
|
||||
end
|
||||
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find_from_wooframework(target_uri)
|
||||
body = Browser.get(target_uri.to_s).body
|
||||
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
|
||||
|
||||
if matches = regexp.match(body)
|
||||
woo_theme_name = matches[1]
|
||||
woo_theme_version = matches[2]
|
||||
#woo_framework_version = matches[3] # Not used at this time
|
||||
|
||||
return new(
|
||||
target_uri,
|
||||
name: woo_theme_name,
|
||||
version: woo_theme_version
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,20 +6,20 @@ class WpTheme
|
||||
# @return [ Void ]
|
||||
def additional_output(verbose = false)
|
||||
parse_style
|
||||
|
||||
|
||||
theme_desc = verbose ? @theme_description : truncate(@theme_description, 100)
|
||||
puts " | Style 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 URI: #@theme_uri" if @theme_uri
|
||||
puts " | Description: #{theme_desc}"
|
||||
puts " | Author: #@theme_author" if @theme_author
|
||||
puts " | Author URI: #@theme_author_uri" if @theme_author_uri
|
||||
puts " | Template: #@theme_template" if @theme_template and verbose
|
||||
puts " | License: #@theme_license" if @theme_license and verbose
|
||||
puts " | License URI: #@theme_license_uri" if @theme_license_uri and verbose
|
||||
puts " | Tags: #@theme_tags" if @theme_tags and verbose
|
||||
puts " | Text Domain: #@theme_text_domain" if @theme_text_domain and verbose
|
||||
puts " | Theme Name: #{@theme_name}" if @theme_name
|
||||
puts " | Theme URI: #{@theme_uri}" if @theme_uri
|
||||
puts " | Description: #{theme_desc}" if theme_desc
|
||||
puts " | Author: #{@theme_author}" if @theme_author
|
||||
puts " | Author URI: #{@theme_author_uri}" if @theme_author_uri
|
||||
puts " | Template: #{@theme_template}" if @theme_template and verbose
|
||||
puts " | License: #{@theme_license}" if @theme_license and verbose
|
||||
puts " | License URI: #{@theme_license_uri}" if @theme_license_uri and verbose
|
||||
puts " | Tags: #{@theme_tags}" if @theme_tags and verbose
|
||||
puts " | Text Domain: #{@theme_text_domain}" if @theme_text_domain and verbose
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Versionable
|
||||
|
||||
def version
|
||||
unless @version
|
||||
@version = Browser.get(style_url).body[%r{Version:\s*([^\s]+)}i, 1]
|
||||
|
||||
# Get Version from readme.txt
|
||||
@version ||= super
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Versionable
|
||||
def version
|
||||
@version ||= Browser.get(style_url).body[%r{Version:\s*(?!trunk)([0-9a-z\.-]+)}i, 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -1,20 +1,20 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_timthumb/versionable'
|
||||
require 'wp_timthumb/existable'
|
||||
require 'wp_timthumb/output'
|
||||
require 'wp_timthumb/vulnerable'
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
include WpTimthumb::Versionable
|
||||
include WpTimthumb::Existable
|
||||
include WpTimthumb::Output
|
||||
include WpTimthumb::Vulnerable
|
||||
|
||||
# @param [ WpTimthumb ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ==(other)
|
||||
url == other.url
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_timthumb/versionable'
|
||||
require 'wp_timthumb/existable'
|
||||
require 'wp_timthumb/output'
|
||||
require 'wp_timthumb/vulnerable'
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
include WpTimthumb::Versionable
|
||||
include WpTimthumb::Existable
|
||||
include WpTimthumb::Output
|
||||
include WpTimthumb::Vulnerable
|
||||
|
||||
# @param [ WpTimthumb ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ==(other)
|
||||
url == other.url
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ class WpTimthumb < WpItem
|
||||
|
||||
def output(verbose = false)
|
||||
puts
|
||||
puts "#{info('[+]')} #{self}" #this will also output the version number if detected
|
||||
puts info("#{self}") #this will also output the version number if detected
|
||||
|
||||
vulnerabilities.output
|
||||
end
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
module Versionable
|
||||
|
||||
# Get the version from the body of an invalid request
|
||||
# See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426
|
||||
#
|
||||
# @return [ String ] The version
|
||||
def version
|
||||
unless @version
|
||||
response = Browser.get(url)
|
||||
@version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1]
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
"#{url}#{ ' v' + version if version}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
module Versionable
|
||||
|
||||
# Get the version from the body of an invalid request
|
||||
# See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426
|
||||
#
|
||||
# @return [ String ] The version
|
||||
def version
|
||||
unless @version
|
||||
response = Browser.get(url)
|
||||
@version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1]
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
"#{url}#{ ' v' + version if version}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ class WpTimthumb < WpItem
|
||||
end
|
||||
|
||||
def check_rce_132
|
||||
return rce_132_vuln unless VersionCompare.lesser_or_equal?('1.33', version)
|
||||
rce_132_vuln unless VersionCompare.lesser_or_equal?('1.33', version)
|
||||
end
|
||||
|
||||
# Vulnerable versions : > 1.35 (or >= 2.0) and < 2.8.14
|
||||
@@ -24,7 +24,7 @@ class WpTimthumb < WpItem
|
||||
|
||||
response = Browser.get(uri.merge('?webshot=1&src=http://' + default_allowed_domains.sample))
|
||||
|
||||
return rce_webshot_vuln unless response.body =~ /WEBSHOT_ENABLED == true/
|
||||
rce_webshot_vuln unless response.body =~ /WEBSHOT_ENABLED == true/
|
||||
end
|
||||
|
||||
# @return [ Array<String> ] The default allowed domains (between the 2.0 and 2.8.13)
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_user/existable'
|
||||
require 'wp_user/brute_forcable'
|
||||
|
||||
class WpUser < WpItem
|
||||
include WpUser::Existable
|
||||
include WpUser::BruteForcable
|
||||
|
||||
attr_accessor :id, :login, :display_name, :password
|
||||
|
||||
# @return [ Array<Symbol> ]
|
||||
def allowed_options; [:id, :login, :display_name, :password] end
|
||||
|
||||
# @return [ URI ] The uri to the author page
|
||||
def uri
|
||||
if id
|
||||
return @uri.merge("?author=#{id}")
|
||||
else
|
||||
raise 'The id is nil'
|
||||
end
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def login_url
|
||||
unless @login_url
|
||||
@login_url = @uri.merge('wp-login.php').to_s
|
||||
|
||||
# Let's check if the login url is redirected (to https url for example)
|
||||
if redirection = redirection(@login_url)
|
||||
@login_url = redirection
|
||||
end
|
||||
end
|
||||
|
||||
@login_url
|
||||
end
|
||||
|
||||
def redirection(url)
|
||||
redirection = nil
|
||||
response = Browser.get(url)
|
||||
|
||||
if response.code == 301 || response.code == 302
|
||||
redirection = response.headers_hash['location']
|
||||
|
||||
# Let's check if there is a redirection in the redirection
|
||||
if other_redirection = redirection(redirection)
|
||||
redirection = other_redirection
|
||||
end
|
||||
end
|
||||
|
||||
redirection
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
s = "#{id}"
|
||||
s << " | #{login}" if login
|
||||
s << " | #{display_name}" if display_name
|
||||
s
|
||||
end
|
||||
|
||||
# @param [ WpUser ] other
|
||||
def <=>(other)
|
||||
id <=> other.id
|
||||
end
|
||||
|
||||
# @param [ WpUser ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ==(other)
|
||||
self === other
|
||||
end
|
||||
|
||||
# @param [ WpUser ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ===(other)
|
||||
id === other.id && login === other.login
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_user/existable'
|
||||
require 'wp_user/brute_forcable'
|
||||
|
||||
class WpUser < WpItem
|
||||
include WpUser::Existable
|
||||
include WpUser::BruteForcable
|
||||
|
||||
attr_accessor :id, :login, :display_name, :password
|
||||
|
||||
# @return [ Array<Symbol> ]
|
||||
def allowed_options; [:id, :login, :display_name, :password] end
|
||||
|
||||
# @return [ URI ] The uri to the author page
|
||||
def uri
|
||||
if id
|
||||
@uri.merge("?author=#{id}")
|
||||
else
|
||||
raise 'The id is nil'
|
||||
end
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def login_url
|
||||
unless @login_url
|
||||
@login_url = @uri.merge('wp-login.php').to_s
|
||||
|
||||
# Let's check if the login url is redirected (to https url for example)
|
||||
if redirection = redirection(@login_url)
|
||||
@login_url = redirection
|
||||
end
|
||||
end
|
||||
|
||||
@login_url
|
||||
end
|
||||
|
||||
def redirection(url)
|
||||
redirection = nil
|
||||
response = Browser.get(url)
|
||||
|
||||
if response.code == 301 || response.code == 302
|
||||
redirection = response.headers_hash['location']
|
||||
|
||||
# Let's check if there is a redirection in the redirection
|
||||
if other_redirection = redirection(redirection)
|
||||
redirection = other_redirection
|
||||
end
|
||||
end
|
||||
|
||||
redirection
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
s = "#{id}"
|
||||
s << " | #{login}" if login
|
||||
s << " | #{display_name}" if display_name
|
||||
s
|
||||
end
|
||||
|
||||
# @param [ WpUser ] other
|
||||
def <=>(other)
|
||||
id <=> other.id
|
||||
end
|
||||
|
||||
# @param [ WpUser ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ==(other)
|
||||
self === other
|
||||
end
|
||||
|
||||
# @param [ WpUser ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ===(other)
|
||||
id === other.id && login === other.login
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
class WpUser < WpItem
|
||||
module BruteForcable
|
||||
|
||||
attr_reader :progress_bar
|
||||
|
||||
# Brute force the user with the wordlist supplied
|
||||
#
|
||||
# It can take a long time to queue 2 million requests,
|
||||
@@ -25,16 +27,17 @@ class WpUser < WpItem
|
||||
hydra = browser.hydra
|
||||
queue_count = 0
|
||||
found = false
|
||||
progress_bar = self.progress_bar(count_file_lines(wordlist), options)
|
||||
|
||||
create_progress_bar(count_file_lines(wordlist)+1, options)
|
||||
|
||||
File.open(wordlist).each do |password|
|
||||
password.chop!
|
||||
password.chomp!
|
||||
|
||||
# A successfull login will redirect us to the redirect_to parameter
|
||||
# Generate a random one on each request
|
||||
unless redirect_url
|
||||
random = (0...8).map { 65.+(rand(26)).chr }.join
|
||||
redirect_url = "#@uri#{random}/"
|
||||
redirect_url = "#{@uri}#{random}/"
|
||||
end
|
||||
|
||||
request = login_request(password, redirect_url)
|
||||
@@ -42,7 +45,7 @@ class WpUser < WpItem
|
||||
request.on_complete do |response|
|
||||
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)
|
||||
found = true
|
||||
@@ -57,22 +60,23 @@ class WpUser < WpItem
|
||||
if queue_count >= browser.max_threads
|
||||
hydra.run
|
||||
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
|
||||
|
||||
# run all of the remaining requests
|
||||
hydra.run
|
||||
puts if options[:show_progression] # mandatory to avoid the output of the progressbar to be overriden
|
||||
end
|
||||
|
||||
# @param [ Integer ] targets_size
|
||||
# @param [ Integer ] passwords_size
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ ProgressBar ]
|
||||
# :nocov:
|
||||
def progress_bar(passwords_size, options)
|
||||
def create_progress_bar(passwords_size, options)
|
||||
if options[:show_progression]
|
||||
ProgressBar.create(
|
||||
@progress_bar = ProgressBar.create(
|
||||
format: '%t %a <%B> (%c / %C) %P%% %e',
|
||||
title: " Brute Forcing '#{login}'",
|
||||
total: passwords_size
|
||||
@@ -106,20 +110,20 @@ class WpUser < WpItem
|
||||
progression = "#{info('[SUCCESS]')} Login : #{login} Password : #{password}\n\n"
|
||||
valid = true
|
||||
elsif response.body =~ /login_error/i
|
||||
verbose = "\n Incorrect login and/or password."
|
||||
verbose = "Incorrect login and/or password."
|
||||
elsif response.timed_out?
|
||||
progression = "#{critical('ERROR:')} Request timed out."
|
||||
progression = critical('ERROR: Request timed out.')
|
||||
elsif response.code == 0
|
||||
progression = "#{critical('ERROR:')} No response from remote server. WAF/IPS?"
|
||||
progression = critical("ERROR: No response from remote server. WAF/IPS? (#{response.return_message})")
|
||||
elsif response.code.to_s =~ /^50/
|
||||
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
|
||||
progression = "#{critical('ERROR:')} We received an unknown response for #{password}..."
|
||||
verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
|
||||
progression = critical("ERROR: We received an unknown response for #{password}...")
|
||||
verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
|
||||
end
|
||||
|
||||
puts "\n " + progression if progression && options[:show_progression]
|
||||
puts verbose if verbose && options[:verbose]
|
||||
progress_bar.log(" #{progression}") if progression && options[:show_progression]
|
||||
progress_bar.log(" #{verbose}") if verbose && options[:verbose]
|
||||
|
||||
valid || false
|
||||
end
|
||||
|
||||
@@ -1,83 +1,86 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUser < WpItem
|
||||
module Existable
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists_from_response?(response, options = {})
|
||||
load_from_response(response)
|
||||
|
||||
@login ? true : false
|
||||
end
|
||||
|
||||
# Load the login and display_name from the response
|
||||
#
|
||||
# @param [ Typhoeus::Response ] response
|
||||
#
|
||||
# @return [ void ]
|
||||
def load_from_response(response)
|
||||
if response.code == 301 # login in location?
|
||||
location = response.headers_hash['Location']
|
||||
|
||||
return if location.nil? || location.empty?
|
||||
|
||||
@login = Existable.login_from_author_pattern(location)
|
||||
@display_name = Existable.display_name_from_body(
|
||||
Browser.get(location).body
|
||||
)
|
||||
elsif response.code == 200 # login in body?
|
||||
@login = Existable.login_from_body(response.body)
|
||||
@display_name = Existable.display_name_from_body(response.body)
|
||||
end
|
||||
end
|
||||
private :load_from_response
|
||||
|
||||
# @param [ String ] text
|
||||
#
|
||||
# @return [ String ] The login
|
||||
def self.login_from_author_pattern(text)
|
||||
text[%r{/author/([^/\b]+)/?}i, 1]
|
||||
end
|
||||
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String ] The login
|
||||
def self.login_from_body(body)
|
||||
# Feed URL with Permalinks
|
||||
login = WpUser::Existable.login_from_author_pattern(body)
|
||||
|
||||
unless login
|
||||
# No Permalinks
|
||||
login = body[%r{<body class="archive author author-([^\s]+) author-(\d+)}i, 1]
|
||||
end
|
||||
|
||||
login
|
||||
end
|
||||
|
||||
# @note Some bodies are encoded in ASCII-8BIT, and Nokogiri doesn't support it
|
||||
# So it's forced to UTF-8 when this encoding is detected
|
||||
#
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String ] The display_name
|
||||
def self.display_name_from_body(body)
|
||||
if title_tag = body[%r{<title>([^<]+)</title>}i, 1]
|
||||
title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT
|
||||
title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s
|
||||
# & are not decoded with Nokogiri
|
||||
title_tag.gsub!('&', '&')
|
||||
|
||||
# replace UTF chars like » with dummy character
|
||||
title_tag.gsub!(/&#(\d+);/, '|')
|
||||
|
||||
name = title_tag[%r{([^|«»]+) }, 1]
|
||||
|
||||
return name.strip if name
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUser < WpItem
|
||||
module Existable
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists_from_response?(response, options = {})
|
||||
load_from_response(response)
|
||||
|
||||
@login ? true : false
|
||||
end
|
||||
|
||||
# Load the login and display_name from the response
|
||||
#
|
||||
# @param [ Typhoeus::Response ] response
|
||||
#
|
||||
# @return [ void ]
|
||||
def load_from_response(response)
|
||||
if response.code == 301 # login in location?
|
||||
location = response.headers_hash['Location']
|
||||
|
||||
return if location.nil? || location.empty?
|
||||
|
||||
@login = Existable.login_from_author_pattern(location)
|
||||
@display_name = Existable.display_name_from_body(
|
||||
Browser.get(location).body
|
||||
)
|
||||
elsif response.code == 200 # login in body?
|
||||
@login = Existable.login_from_body(response.body)
|
||||
@display_name = Existable.display_name_from_body(response.body)
|
||||
end
|
||||
end
|
||||
private :load_from_response
|
||||
|
||||
# @param [ String ] text
|
||||
#
|
||||
# @return [ String ] The login
|
||||
def self.login_from_author_pattern(text)
|
||||
return unless text =~ %r{/author/([^/\b"']+)/?}i
|
||||
|
||||
Regexp.last_match[1].force_encoding('UTF-8')
|
||||
end
|
||||
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String ] The login
|
||||
def self.login_from_body(body)
|
||||
# Feed URL with Permalinks
|
||||
login = WpUser::Existable.login_from_author_pattern(body)
|
||||
|
||||
unless login
|
||||
# No Permalinks
|
||||
login = body[%r{<body class="archive author author-([^\s]+)[ "]}i, 1]
|
||||
login ? login.force_encoding('UTF-8') : nil
|
||||
end
|
||||
|
||||
login
|
||||
end
|
||||
|
||||
# @note Some bodies are encoded in ASCII-8BIT, and Nokogiri doesn't support it
|
||||
# So it's forced to UTF-8 when this encoding is detected
|
||||
#
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String ] The display_name
|
||||
def self.display_name_from_body(body)
|
||||
if title_tag = body[%r{<title>([^<]+)</title>}i, 1]
|
||||
title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT
|
||||
title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s
|
||||
# & are not decoded with Nokogiri
|
||||
title_tag.gsub!('&', '&')
|
||||
|
||||
# replace UTF chars like » with dummy character
|
||||
title_tag.gsub!(/&#(\d+);/, '|')
|
||||
|
||||
name = title_tag[%r{([^|«»]+) }, 1]
|
||||
|
||||
return name.strip if name
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,26 +1,48 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_version/findable'
|
||||
require 'wp_version/vulnerable'
|
||||
require 'wp_version/output'
|
||||
|
||||
class WpVersion < WpItem
|
||||
|
||||
extend WpVersion::Findable
|
||||
include WpVersion::Vulnerable
|
||||
include WpVersion::Output
|
||||
|
||||
# The version number
|
||||
attr_accessor :number
|
||||
|
||||
# @return [ Array ]
|
||||
def allowed_options; super << :number << :found_from end
|
||||
|
||||
# @param [ WpVersion ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ==(other)
|
||||
number == other.number
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_version/findable'
|
||||
require 'wp_version/output'
|
||||
|
||||
class WpVersion < WpItem
|
||||
extend WpVersion::Findable
|
||||
include WpVersion::Output
|
||||
|
||||
# The version number
|
||||
attr_accessor :number, :metadata
|
||||
alias_method :version, :number # Needed to have the right behaviour in Vulnerable#vulnerable_to?
|
||||
|
||||
# @return [ Array ]
|
||||
def allowed_options; super << :number << :found_from end
|
||||
|
||||
def identifier
|
||||
@identifier ||= number
|
||||
end
|
||||
|
||||
def db_file
|
||||
@db_file ||= WORDPRESSES_FILE
|
||||
end
|
||||
|
||||
# @param [ WpVersion ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ==(other)
|
||||
number == other.number
|
||||
end
|
||||
|
||||
# @return [ Array<String> ] All the stable versions from version_file
|
||||
def self.all(versions_file = WP_VERSIONS_FILE)
|
||||
Nokogiri.XML(File.open(versions_file)).css('version').reduce([]) do |a, node|
|
||||
a << node.text.to_s
|
||||
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
|
||||
|
||||
@@ -1,218 +1,219 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpVersion < WpItem
|
||||
|
||||
module Findable
|
||||
|
||||
# Find the version of the blog designated from target_uri
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ String ] wp_content_dir
|
||||
# @param [ String ] wp_plugins_dir
|
||||
#
|
||||
# @return [ WpVersion ]
|
||||
def find(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
methods.grep(/^find_from_/).each do |method|
|
||||
|
||||
if method === :find_from_advanced_fingerprinting
|
||||
version = send(method, target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
else
|
||||
version = send(method, target_uri)
|
||||
end
|
||||
|
||||
if version
|
||||
return new(target_uri, number: version, found_from: method)
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Used to check if the version is correct: must contain at least one dot.
|
||||
#
|
||||
# @return [ String ]
|
||||
def version_pattern
|
||||
'([^\r\n"\']+\.[^\r\n"\']+)'
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Returns the first match of <pattern> in the body of the url
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ Regex ] pattern
|
||||
# @param [ String ] path
|
||||
#
|
||||
# @return [ String ]
|
||||
def scan_url(target_uri, pattern, path = nil)
|
||||
url = path ? target_uri.merge(path).to_s : target_uri.to_s
|
||||
response = Browser.get_and_follow_location(url)
|
||||
|
||||
response.body[pattern, 1]
|
||||
end
|
||||
|
||||
#
|
||||
# DO NOT Change the order of the following methods
|
||||
# unless you know what you are doing
|
||||
# See WpVersion.find
|
||||
#
|
||||
|
||||
# Attempts to find the wordpress version from,
|
||||
# the generator meta tag in the html source.
|
||||
#
|
||||
# The meta tag can be removed however it seems,
|
||||
# that it is reinstated on upgrade.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_meta_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{name="generator" content="wordpress #{version_pattern}"}i
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS feed source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_rss_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<generator>http://wordpress.org/\?v=#{version_pattern}</generator>}i,
|
||||
'feed/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find WordPress version from,
|
||||
# the generator tag in the RDF feed source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_rdf_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{version_pattern}" />}i,
|
||||
'feed/rdf/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS2 feed source.
|
||||
#
|
||||
# Have not been able to find an example of this - Ryan
|
||||
#def find_from_rss2_generator(target_uri)
|
||||
# scan_url(
|
||||
# target_uri,
|
||||
# %r{<generator>http://wordpress.org/?v=(#{WpVersion.version_pattern})</generator>}i,
|
||||
# 'feed/rss/'
|
||||
# )
|
||||
#end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the Atom source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_atom_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<generator uri="http://wordpress.org/" version="#{version_pattern}">WordPress</generator>}i,
|
||||
'feed/atom/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the comment rss source.
|
||||
#
|
||||
# Have not been able to find an example of this - Ryan
|
||||
#def find_from_comments_rss_generator(target_uri)
|
||||
# scan_url(
|
||||
# target_uri,
|
||||
# %r{<!-- generator="WordPress/#{WpVersion.version_pattern}" -->}i,
|
||||
# 'comments/feed/'
|
||||
# )
|
||||
#end
|
||||
|
||||
# Uses data/wp_versions.xml to try to identify a
|
||||
# wordpress version.
|
||||
#
|
||||
# It does this by using client side file hashing
|
||||
#
|
||||
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ String ] wp_content_dir
|
||||
# @param [ String ] wp_plugins_dir
|
||||
# @param [ String ] versions_xml The path to the xml containing all versions
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, 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_content_dir: wp_content_dir,
|
||||
wp_plugins_dir: wp_plugins_dir)
|
||||
|
||||
xml.xpath('//file').each do |node|
|
||||
wp_item.path = node.attribute('src').text
|
||||
|
||||
response = Browser.get(wp_item.url)
|
||||
md5sum = Digest::MD5.hexdigest(response.body)
|
||||
|
||||
node.search('hash').each do |hash|
|
||||
if hash.attribute('md5').text == md5sum
|
||||
return hash.search('version').text
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the readme.html file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_readme(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<br />\sversion #{version_pattern}}i,
|
||||
'readme.html'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the sitemap.xml file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_sitemap_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{generator="wordpress/#{version_pattern}"}i,
|
||||
'sitemap.xml'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the p-links-opml.php file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_links_opml(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{generator="wordpress/#{version_pattern}"}i,
|
||||
'wp-links-opml.php'
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpVersion < WpItem
|
||||
|
||||
module Findable
|
||||
|
||||
# Find the version of the blog designated from target_uri
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ String ] wp_content_dir
|
||||
# @param [ String ] wp_plugins_dir
|
||||
#
|
||||
# @return [ WpVersion ]
|
||||
def find(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
methods.grep(/^find_from_/).each do |method|
|
||||
|
||||
if method === :find_from_advanced_fingerprinting
|
||||
version = send(method, target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
else
|
||||
version = send(method, target_uri)
|
||||
end
|
||||
|
||||
if version
|
||||
return new(target_uri, number: version, found_from: method)
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Used to check if the version is correct: must contain at least one dot.
|
||||
#
|
||||
# @return [ String ]
|
||||
def version_pattern
|
||||
'([^\r\n"\',]+\.[^\r\n"\',]+)'
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Returns the first match of <pattern> in the body of the url
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ Regex ] pattern
|
||||
# @param [ String ] path
|
||||
#
|
||||
# @return [ String ]
|
||||
def scan_url(target_uri, pattern, path = nil)
|
||||
url = path ? target_uri.merge(path).to_s : target_uri.to_s
|
||||
response = Browser.get_and_follow_location(url)
|
||||
|
||||
response.body[pattern, 1]
|
||||
end
|
||||
|
||||
#
|
||||
# DO NOT Change the order of the following methods
|
||||
# unless you know what you are doing
|
||||
# See WpVersion.find
|
||||
#
|
||||
|
||||
# Attempts to find the wordpress version from,
|
||||
# the generator meta tag in the html source.
|
||||
#
|
||||
# The meta tag can be removed however it seems,
|
||||
# that it is reinstated on upgrade.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_meta_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{name="generator" content="wordpress #{version_pattern}.*"}i
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS feed source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_rss_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<generator>http://wordpress.org/\?v=#{version_pattern}</generator>}i,
|
||||
'feed/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find WordPress version from,
|
||||
# the generator tag in the RDF feed source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_rdf_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{version_pattern}" />}i,
|
||||
'feed/rdf/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the Atom source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_atom_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<generator uri="http://wordpress.org/" version="#{version_pattern}">WordPress</generator>}i,
|
||||
'feed/atom/'
|
||||
)
|
||||
end
|
||||
|
||||
# Uses data/wp_versions.xml to try to identify a
|
||||
# wordpress version.
|
||||
#
|
||||
# It does this by using client side file hashing
|
||||
#
|
||||
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ String ] wp_content_dir
|
||||
# @param [ String ] wp_plugins_dir
|
||||
# @param [ String ] versions_xml The path to the xml containing all versions
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
xml = xml(versions_xml)
|
||||
|
||||
wp_item = WpItem.new(target_uri,
|
||||
wp_content_dir: wp_content_dir,
|
||||
wp_plugins_dir: wp_plugins_dir)
|
||||
|
||||
xml.xpath('//file').each do |node|
|
||||
wp_item.path = node.attribute('src').text
|
||||
|
||||
response = Browser.get(wp_item.url)
|
||||
md5sum = Digest::MD5.hexdigest(response.body)
|
||||
|
||||
node.search('hash').each do |hash|
|
||||
if hash.attribute('md5').text == md5sum
|
||||
return hash.search('version').text
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the readme.html file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_readme(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<br />\sversion #{version_pattern}}i,
|
||||
'readme.html'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the sitemap.xml file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_sitemap_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{generator="wordpress/#{version_pattern}"}i,
|
||||
'sitemap.xml'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the p-links-opml.php file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_links_opml(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{generator="wordpress/#{version_pattern}"}i,
|
||||
'wp-links-opml.php'
|
||||
)
|
||||
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
|
||||
|
||||
@@ -4,14 +4,25 @@ class WpVersion < WpItem
|
||||
module Output
|
||||
|
||||
def output(verbose = false)
|
||||
metadata = self.metadata(self.number)
|
||||
|
||||
puts
|
||||
puts "#{info('[+]')} WordPress version #{self.number} identified from #{self.found_from}"
|
||||
if verbose
|
||||
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
|
||||
|
||||
unless vulnerabilities.empty?
|
||||
puts "#{critical('[!]')} #{vulnerabilities.size} vulnerabilities identified from the version number"
|
||||
|
||||
if vulnerabilities.size == 1
|
||||
puts critical("#{vulnerabilities.size} vulnerability identified from the version number")
|
||||
else
|
||||
puts critical("#{vulnerabilities.size} vulnerabilities identified from the version number")
|
||||
end
|
||||
vulnerabilities.output
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -11,8 +11,8 @@ class VersionCompare
|
||||
# @return [ Boolean ]
|
||||
def self.lesser_or_equal?(version1, version2)
|
||||
# Prepend a '0' if the version starts with a '.'
|
||||
version1 = "0#{version1}" if version1 && version1[0,1] == '.'
|
||||
version2 = "0#{version2}" if version2 && version2[0,1] == '.'
|
||||
version1 = prepend_zero(version1)
|
||||
version2 = prepend_zero(version2)
|
||||
|
||||
return true if (version1 == version2)
|
||||
# Both versions must be set
|
||||
@@ -27,4 +27,36 @@ class VersionCompare
|
||||
end
|
||||
return false
|
||||
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
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
require 'rubygems'
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
@@ -29,12 +30,14 @@ begin
|
||||
require 'shellwords'
|
||||
require 'fileutils'
|
||||
require 'pathname'
|
||||
require 'cgi'
|
||||
# Third party libs
|
||||
require 'typhoeus'
|
||||
require 'json'
|
||||
require 'yajl/json_gem'
|
||||
require 'nokogiri'
|
||||
require 'terminal-table'
|
||||
require 'ruby-progressbar'
|
||||
require 'addressable/uri'
|
||||
# Custom libs
|
||||
require 'common/browser'
|
||||
require 'common/custom_option_parser'
|
||||
|
||||
@@ -54,10 +54,9 @@ class WebSite
|
||||
|
||||
redirected_uri = URI.parse(add_trailing_slash(add_http_protocol(url)))
|
||||
if response.code == 301 || response.code == 302
|
||||
redirection = response.headers_hash['location']
|
||||
if redirection[0] == '/'
|
||||
redirection = "#{redirected_uri.scheme}://#{redirected_uri.host}#{redirection}"
|
||||
end
|
||||
redirection = redirected_uri.merge(response.headers_hash['location']).to_s
|
||||
|
||||
return redirection if url == redirection # prevents infinite loop
|
||||
|
||||
# Let's check if there is a redirection in the redirection
|
||||
if other_redirection = redirection(redirection)
|
||||
|
||||
@@ -15,7 +15,6 @@ class WebSite
|
||||
@uri.clone.merge('robots.txt').to_s
|
||||
end
|
||||
|
||||
|
||||
# Parse robots.txt
|
||||
# @return [ Array ] URLs generated from robots.txt
|
||||
def parse_robots_txt
|
||||
@@ -29,6 +28,7 @@ class WebSite
|
||||
if entries
|
||||
entries.flatten!
|
||||
entries.compact.sort!
|
||||
entries.uniq!
|
||||
wordpress_path = @uri.path
|
||||
RobotsTxt.known_dirs.each do |d|
|
||||
entries.delete(d)
|
||||
@@ -40,9 +40,9 @@ class WebSite
|
||||
entries.each do |d|
|
||||
begin
|
||||
temp = @uri.clone
|
||||
temp.path = d
|
||||
temp.path = d.strip
|
||||
rescue URI::Error
|
||||
temp = d
|
||||
temp = d.strip
|
||||
end
|
||||
return_object << temp.to_s
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'web_site'
|
||||
require 'wp_target/malwares'
|
||||
require 'wp_target/wp_readme'
|
||||
require 'wp_target/wp_registrable'
|
||||
require 'wp_target/wp_config_backup'
|
||||
@@ -11,7 +10,6 @@ require 'wp_target/wp_custom_directories'
|
||||
require 'wp_target/wp_full_path_disclosure'
|
||||
|
||||
class WpTarget < WebSite
|
||||
include WpTarget::Malwares
|
||||
include WpTarget::WpReadme
|
||||
include WpTarget::WpRegistrable
|
||||
include WpTarget::WpConfigBackup
|
||||
@@ -23,14 +21,20 @@ class WpTarget < WebSite
|
||||
attr_reader :verbose
|
||||
|
||||
def initialize(target_url, options = {})
|
||||
raise Exception.new('target_url can not be nil or empty') if target_url.nil? || target_url == ''
|
||||
super(target_url)
|
||||
|
||||
@verbose = options[:verbose]
|
||||
@wp_content_dir = options[:wp_content_dir]
|
||||
@wp_plugins_dir = options[:wp_plugins_dir]
|
||||
@multisite = nil
|
||||
@vhost = options[:vhost]
|
||||
|
||||
Browser.instance.referer = url
|
||||
if @vhost
|
||||
Browser.instance.vhost = @vhost
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# check if the target website is
|
||||
@@ -42,10 +46,12 @@ class WpTarget < WebSite
|
||||
|
||||
# Note: in the future major WPScan version, change the user-agent to see
|
||||
# if the response is a 200 ?
|
||||
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' if response.code == 403
|
||||
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
|
||||
|
||||
if response.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i
|
||||
dir = wp_content_dir ? wp_content_dir : 'wp-content'
|
||||
|
||||
if response.body =~ /["'][^"']*\/#{Regexp.escape(dir)}\/[^"']*["']/i
|
||||
wordpress = true
|
||||
else
|
||||
|
||||
@@ -72,9 +78,7 @@ class WpTarget < WebSite
|
||||
|
||||
# Let's check if the login url is redirected (to https url for example)
|
||||
redirection = redirection(url)
|
||||
if redirection
|
||||
url = redirection
|
||||
end
|
||||
url = redirection if redirection
|
||||
|
||||
url
|
||||
end
|
||||
@@ -131,6 +135,11 @@ class WpTarget < WebSite
|
||||
@uri.merge("#{wp_content_dir}/uploads/").to_s
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def includes_dir_url
|
||||
@uri.merge("wp-includes/").to_s
|
||||
end
|
||||
|
||||
# Script for replacing strings in wordpress databases
|
||||
# reveals database credentials after hitting submit
|
||||
# http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
|
||||
@@ -149,4 +158,8 @@ class WpTarget < WebSite
|
||||
def upload_directory_listing_enabled?
|
||||
directory_listing_enabled?(upload_dir_url)
|
||||
end
|
||||
|
||||
def include_directory_listing_enabled?
|
||||
directory_listing_enabled?(includes_dir_url)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTarget < WebSite
|
||||
module Malwares
|
||||
# Used as cache :
|
||||
# nil => malwares not checked,
|
||||
# [] => no malwares,
|
||||
# otherwise array of malwares url found
|
||||
@malwares = nil
|
||||
|
||||
def has_malwares?(malwares_file_path = nil)
|
||||
!malwares(malwares_file_path).empty?
|
||||
end
|
||||
|
||||
# return array of string (url of malwares found)
|
||||
def malwares(malwares_file_path = nil)
|
||||
unless @malwares
|
||||
malwares_found = []
|
||||
malwares_file = Malwares.malwares_file(malwares_file_path)
|
||||
index_page_body = Browser.get(@uri.to_s).body
|
||||
|
||||
File.open(malwares_file, 'r') do |file|
|
||||
file.readlines.collect do |url|
|
||||
chomped_url = url.chomp
|
||||
|
||||
if chomped_url.length > 0
|
||||
malwares_found += index_page_body.scan(Malwares.malware_pattern(chomped_url))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
malwares_found.flatten!
|
||||
malwares_found.uniq!
|
||||
|
||||
@malwares = malwares_found
|
||||
end
|
||||
@malwares
|
||||
end
|
||||
|
||||
def self.malwares_file(malwares_file_path)
|
||||
malwares_file_path || DATA_DIR + '/malwares.txt'
|
||||
end
|
||||
|
||||
def self.malware_pattern(url_regex)
|
||||
# no need to escape regex here, because malware.txt contains regex
|
||||
%r{<(?:script|iframe).* src=(?:"|')(#{url_regex}[^"']*)(?:"|')[^>]*>}i
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -14,7 +14,7 @@ class WpTarget < WebSite
|
||||
queue_count = 0
|
||||
|
||||
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.on_complete do |response|
|
||||
@@ -40,7 +40,7 @@ class WpTarget < WebSite
|
||||
# @return [ Array ]
|
||||
def self.config_backup_files
|
||||
%w{
|
||||
wp-config.php~ #wp-config.php# wp-config.php.save .wp-config.php.swp wp-config.php.swp wp-config.php.swo
|
||||
wp-config.php~ #wp-config.php# wp-config.php.save .wp-config.php.swp wp-config.php.swp wp-config.php.swo
|
||||
wp-config.php_bak wp-config.bak wp-config.php.bak wp-config.save wp-config.old wp-config.php.old
|
||||
wp-config.php.orig wp-config.orig wp-config.php.original wp-config.original wp-config.txt
|
||||
} # thanks to Feross.org for these
|
||||
|
||||
@@ -23,9 +23,9 @@ class WpTarget < WebSite
|
||||
# @return [ Boolean ]
|
||||
def default_wp_content_dir_exists?
|
||||
response = Browser.get(@uri.merge('wp-content').to_s)
|
||||
hash = Digest::MD5.hexdigest(response.body)
|
||||
|
||||
if WpTarget.valid_response_codes.include?(response.code)
|
||||
hash = WebSite.page_hash(response)
|
||||
return true if hash != error_404_hash and hash != homepage_hash
|
||||
end
|
||||
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
|
||||
class WpTarget < WebSite
|
||||
module WpFullPathDisclosure
|
||||
|
||||
# Check for Full Path Disclosure (FPD)
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def has_full_path_disclosure?
|
||||
response = Browser.get(full_path_disclosure_url())
|
||||
response.body[%r{Fatal error}i] ? true : false
|
||||
Browser.get(full_path_disclosure_url).body[%r/Fatal error/i] ? true : false
|
||||
end
|
||||
|
||||
def full_path_disclosure_data
|
||||
return nil unless has_full_path_disclosure?
|
||||
Browser.get(full_path_disclosure_url).body[/Fatal error:.+? in (.+?) on/i, 1]
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def full_path_disclosure_url
|
||||
@uri.merge('wp-includes/rss-functions.php').to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ class WpTarget < WebSite
|
||||
@login_protection_plugin = nil
|
||||
|
||||
def has_login_protection?
|
||||
!login_protection_plugin().nil?
|
||||
!login_protection_plugin.nil?
|
||||
end
|
||||
|
||||
# Checks if a login protection plugin is enabled
|
||||
@@ -74,7 +74,7 @@ class WpTarget < WebSite
|
||||
|
||||
# http://wordpress.org/extend/plugins/login-security-solution/
|
||||
def has_login_security_solution_protection?
|
||||
Browser.get(login_security_solution_url()).code != 404
|
||||
Browser.get(login_security_solution_url).code != 404
|
||||
end
|
||||
|
||||
def login_security_solution_url
|
||||
@@ -99,5 +99,12 @@ class WpTarget < WebSite
|
||||
plugin_url('bluetrait-event-viewer')
|
||||
end
|
||||
|
||||
# https://wordpress.org/plugins/security-protection/
|
||||
def has_security_protection_protection?
|
||||
Nokogiri::HTML(Browser.get(login_url).body).css('script').each do |node|
|
||||
return true if node['src'] =~ /security-protection.js/i
|
||||
end
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
|
||||
class WpTarget < WebSite
|
||||
module WpMustUsePlugins
|
||||
|
||||
# Checks to see if the must use plugin folder exists
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def has_must_use_plugins?
|
||||
response = Browser.get(must_use_url)
|
||||
|
||||
if response && WpTarget.valid_response_codes.include?(response.code)
|
||||
hash = WebSite.page_hash(response.body)
|
||||
if response && [200, 401, 403].include?(response.code)
|
||||
hash = WebSite.page_hash(response)
|
||||
return true if hash != error_404_hash && hash != homepage_hash
|
||||
end
|
||||
|
||||
@@ -21,6 +20,5 @@ class WpTarget < WebSite
|
||||
def must_use_url
|
||||
@uri.merge("#{wp_content_dir}/mu-plugins/").to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ class WpTarget < WebSite
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def has_readme?
|
||||
response = Browser.get(readme_url())
|
||||
response = Browser.get(readme_url)
|
||||
|
||||
unless response.code == 404
|
||||
return response.body =~ %r{wordpress}i ? true : false
|
||||
|
||||
@@ -62,7 +62,7 @@ def help
|
||||
puts
|
||||
puts 'Some values are settable in a config file, see the example.conf.json'
|
||||
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 '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
|
||||
puts '--enumerate | -e [option(s)] Enumeration.'
|
||||
@@ -97,22 +97,36 @@ def help
|
||||
puts ' If no protocol is given (format host:port), HTTP will be used.'
|
||||
puts '--proxy-auth <username:password> Supply the proxy login credentials.'
|
||||
puts '--basic-auth <username:password> Set the HTTP Basic authentication.'
|
||||
puts '--wordlist | -w <wordlist> Supply a wordlist for the password bruter and do the brute.'
|
||||
puts '--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.'
|
||||
puts '--username | -U <username> Only brute force the supplied username.'
|
||||
puts '--usernames <path-to-file> Only brute force the usernames from the file.'
|
||||
puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests.'
|
||||
puts '--cache-ttl <cache-ttl> Typhoeus cache TTL.'
|
||||
puts '--request-timeout <request-timeout> Request Timeout.'
|
||||
puts '--connect-timeout <connect-timeout> Connect Timeout.'
|
||||
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 '--verbose | -v Verbose output.'
|
||||
puts '--version Output the current version and exit.'
|
||||
puts
|
||||
end
|
||||
|
||||
# Hook to check if the target if down during the scan
|
||||
# The target is considered down after 10 requests with status = 0
|
||||
down = 0
|
||||
# And have the number of requests performed to display at the end of the scan
|
||||
# The target is considered down after 30 requests with status = 0
|
||||
down = 0
|
||||
@total_requests_done = 0
|
||||
|
||||
Typhoeus.on_complete do |response|
|
||||
next if response.cached?
|
||||
|
||||
down += 1 if response.code == 0
|
||||
fail 'The target seems to be down' if down >= 10
|
||||
@total_requests_done += 1
|
||||
|
||||
fail 'The target seems to be down' if down >= 30
|
||||
|
||||
next unless Browser.instance.throttle > 0
|
||||
|
||||
sleep(Browser.instance.throttle)
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpscanOptions
|
||||
|
||||
ACCESSOR_OPTIONS = [
|
||||
:batch,
|
||||
:enumerate_plugins,
|
||||
@@ -14,15 +13,18 @@ class WpscanOptions
|
||||
:enumerate_usernames,
|
||||
:enumerate_usernames_range,
|
||||
:no_color,
|
||||
:log,
|
||||
:proxy,
|
||||
:proxy_auth,
|
||||
:threads,
|
||||
:url,
|
||||
:vhost,
|
||||
:wordlist,
|
||||
:force,
|
||||
:update,
|
||||
:verbose,
|
||||
:username,
|
||||
:usernames,
|
||||
:password,
|
||||
:follow_redirection,
|
||||
:wp_content_dir,
|
||||
@@ -39,7 +41,9 @@ class WpscanOptions
|
||||
:cache_ttl,
|
||||
:request_timeout,
|
||||
:connect_timeout,
|
||||
:max_threads
|
||||
:max_threads,
|
||||
:no_banner,
|
||||
:throttle
|
||||
]
|
||||
|
||||
attr_accessor *ACCESSOR_OPTIONS
|
||||
@@ -51,11 +55,17 @@ class WpscanOptions
|
||||
end
|
||||
|
||||
def url=(url)
|
||||
raise 'Empty URL given' if !url
|
||||
raise Exception.new('Empty URL given') if url.nil? || url == ''
|
||||
|
||||
url = Addressable::URI.parse(url).normalize.to_s unless url.ascii_only?
|
||||
|
||||
@url = URI.parse(add_http_protocol(url)).to_s
|
||||
end
|
||||
|
||||
def vhost=(vhost)
|
||||
@vhost = vhost
|
||||
end
|
||||
|
||||
def threads=(threads)
|
||||
@threads = threads.is_a?(Integer) ? threads : threads.to_i
|
||||
end
|
||||
@@ -68,6 +78,12 @@ class WpscanOptions
|
||||
end
|
||||
end
|
||||
|
||||
def usernames=(file)
|
||||
fail "The file #{file} does not exist" unless File.exists?(file)
|
||||
|
||||
@usernames = file
|
||||
end
|
||||
|
||||
def proxy=(proxy)
|
||||
if proxy.index(':') == nil
|
||||
raise 'Invalid proxy format. Should be host:port.'
|
||||
@@ -235,8 +251,10 @@ class WpscanOptions
|
||||
def self.get_opt_long
|
||||
GetoptLong.new(
|
||||
['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--vhost',GetoptLong::OPTIONAL_ARGUMENT],
|
||||
['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT],
|
||||
['--username', '-U', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--usernames', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--wordlist', '-w', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--threads', '-t', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--force', '-f', GetoptLong::NO_ARGUMENT],
|
||||
@@ -261,7 +279,10 @@ class WpscanOptions
|
||||
['--max-threads', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--batch', GetoptLong::NO_ARGUMENT],
|
||||
['--no-color', GetoptLong::NO_ARGUMENT],
|
||||
['--cookie', GetoptLong::REQUIRED_ARGUMENT]
|
||||
['--cookie', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--log', GetoptLong::NO_ARGUMENT],
|
||||
['--no-banner', GetoptLong::NO_ARGUMENT],
|
||||
['--throttle', GetoptLong::REQUIRED_ARGUMENT]
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class CheckerPlugin < Plugin
|
||||
|
||||
def initialize
|
||||
super(author: 'WPScanTeam - @erwanlr')
|
||||
|
||||
register_options(
|
||||
['--check-vuln-ref-urls', '--cvru', 'Check all the vulnerabilities reference urls for 404'],
|
||||
['--check-local-vulnerable-files LOCAL_DIRECTORY', '--clvf', 'Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells']
|
||||
)
|
||||
end
|
||||
|
||||
def run(options = {})
|
||||
if options[:check_vuln_ref_urls]
|
||||
check_vuln_ref_urls
|
||||
end
|
||||
|
||||
if options[:check_local_vulnerable_files]
|
||||
check_local_vulnerable_files(options[:check_local_vulnerable_files])
|
||||
end
|
||||
end
|
||||
|
||||
def check_vuln_ref_urls
|
||||
vuln_ref_files = [PLUGINS_VULNS_FILE, THEMES_VULNS_FILE, WP_VULNS_FILE]
|
||||
error_codes = [404, 500, 403]
|
||||
not_found_regexp = %r{No Results Found|error 404|ID Invalid or Not Found}i
|
||||
|
||||
puts '[+] Checking vulnerabilities reference urls'
|
||||
|
||||
vuln_ref_files.each do |vuln_ref_file|
|
||||
json = json(vuln_ref_file)
|
||||
|
||||
urls = []
|
||||
json.each do |asset|
|
||||
asset[asset.keys.inject]['vulnerabilities'].each do |url|
|
||||
unless url['url'].nil?
|
||||
url['url'].each do |url|
|
||||
urls << url
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
urls.uniq!
|
||||
|
||||
puts "[!] No URLs found in #{vuln_ref_file}!" if urls.empty?
|
||||
|
||||
dead_urls = []
|
||||
queue_count = 0
|
||||
request_count = 0
|
||||
browser = Browser.instance
|
||||
hydra = browser.hydra
|
||||
number_of_urls = urls.size
|
||||
|
||||
urls.each do |url|
|
||||
request = browser.forge_request(url, { cache_ttl: 0, followlocation: true })
|
||||
request_count += 1
|
||||
|
||||
request.on_complete do |response|
|
||||
print "\r [+] Checking #{vuln_ref_file} #{number_of_urls} total ... #{(request_count * 100) / number_of_urls}% complete."
|
||||
|
||||
if error_codes.include?(response.code) or not_found_regexp.match(response.body)
|
||||
dead_urls << url
|
||||
end
|
||||
end
|
||||
|
||||
hydra.queue(request)
|
||||
queue_count += 1
|
||||
|
||||
if queue_count == browser.max_threads
|
||||
hydra.run
|
||||
queue_count = 0
|
||||
end
|
||||
end
|
||||
|
||||
hydra.run
|
||||
puts
|
||||
unless dead_urls.empty?
|
||||
dead_urls.each { |url| puts " Not Found #{url}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_local_vulnerable_files(dir_to_scan)
|
||||
if Dir.exist?(dir_to_scan)
|
||||
xml_file = LOCAL_FILES_FILE
|
||||
local_hashes = {}
|
||||
file_extension_to_scan = '*.{js,php,swf,html,htm}'
|
||||
|
||||
print '[+] Generating local hashes ... '
|
||||
|
||||
Dir[File.join(dir_to_scan, '**', file_extension_to_scan)]
|
||||
.select { |f| File.file?(f) }
|
||||
.each do |filename|
|
||||
sha1sum = Digest::SHA1.file(filename).hexdigest
|
||||
|
||||
if local_hashes.key?(sha1sum)
|
||||
local_hashes[sha1sum] << filename
|
||||
else
|
||||
local_hashes[sha1sum] = [filename]
|
||||
end
|
||||
end
|
||||
|
||||
puts 'done.'
|
||||
|
||||
puts '[+] Checking for vulnerable files ...'
|
||||
|
||||
xml = xml(xml_file)
|
||||
|
||||
xml.xpath('//hash').each do |node|
|
||||
sha1sum = node.attribute('sha1').text
|
||||
|
||||
if local_hashes.has_key?(sha1sum)
|
||||
local_filenames = local_hashes[sha1sum]
|
||||
vuln_title = node.search('title').text
|
||||
vuln_filename = node.search('file').text
|
||||
vuln_refrence = node.search('reference').text
|
||||
|
||||
puts " #{vuln_filename} found :"
|
||||
puts ' | Location(s):'
|
||||
local_filenames.each do |file|
|
||||
puts " | - #{file}"
|
||||
end
|
||||
puts ' |'
|
||||
puts " | Title: #{vuln_title}"
|
||||
puts " | Refrence: #{vuln_refrence}" if !vuln_refrence.empty?
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
puts 'done.'
|
||||
|
||||
else
|
||||
puts "The supplied directory '#{dir_to_scan}' does not exist"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,91 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class CheckerSpelling < Plugin
|
||||
|
||||
def initialize
|
||||
super(author: 'WPScanTeam - @ethicalhack3r')
|
||||
|
||||
register_options(['--spellcheck', '--sc', 'Check all files for common spelling mistakes.'])
|
||||
end
|
||||
|
||||
def run(options = {})
|
||||
spellcheck if options[:spellcheck]
|
||||
end
|
||||
|
||||
def spellcheck
|
||||
mistakes = 0
|
||||
|
||||
puts '[+] Checking for spelling mistakes'
|
||||
puts
|
||||
|
||||
files.each do |file_name|
|
||||
if File.exists?(file_name)
|
||||
file = File.open(file_name, 'r')
|
||||
|
||||
misspellings.each_key do |misspelling|
|
||||
begin
|
||||
file.read.scan(/#{misspelling}/).each do |match|
|
||||
mistakes += 1
|
||||
puts "[MISSPELLING] File: #{file_name} Bad: #{match} Good: #{misspellings[misspelling]}"
|
||||
end
|
||||
rescue => e
|
||||
puts "Error in #{file_name} #{e}"
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
file.close
|
||||
end
|
||||
end
|
||||
|
||||
puts
|
||||
puts "[+] Found #{mistakes} spelling mistakes"
|
||||
|
||||
mistakes
|
||||
end
|
||||
|
||||
def misspellings
|
||||
{
|
||||
/databse/i => 'database',
|
||||
/whith/i => 'with',
|
||||
/wich/i => 'which',
|
||||
/verions/i => 'versions',
|
||||
/vulnerabilitiy/i => 'vulnerability',
|
||||
/unkown/i => 'unknown',
|
||||
/recieved/i => 'received',
|
||||
/acheive/i => 'achieve',
|
||||
/wierd/i => 'weird',
|
||||
/untill/i => 'until',
|
||||
/alot/i => 'a lot',
|
||||
/randomstorm/ => 'RandomStorm',
|
||||
/wpscan/ => 'WPScan',
|
||||
/Wordpress/ => 'WordPress'
|
||||
}
|
||||
end
|
||||
|
||||
def files
|
||||
files = Dir['**/*'].reject {|fn| File.directory?(fn) }
|
||||
|
||||
ignore.each do |ignore|
|
||||
files.delete_if { |data| data.match(ignore) }
|
||||
end
|
||||
|
||||
files
|
||||
end
|
||||
|
||||
def ignore
|
||||
ignore = []
|
||||
|
||||
ignore << File.basename(__FILE__)
|
||||
ignore << 'spec/cache/'
|
||||
ignore << 'spec/spec_session/'
|
||||
ignore << 'cache/'
|
||||
ignore << 'coverage/'
|
||||
ignore << 'wordlist-iso-8859-1'
|
||||
ignore << 'log.txt'
|
||||
ignore << 'debug.log'
|
||||
ignore << 'wordlist.txt'
|
||||
|
||||
ignore
|
||||
end
|
||||
end
|
||||
@@ -1,106 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class StatsPlugin < Plugin
|
||||
|
||||
def initialize
|
||||
super(author: 'WPScanTeam - Christian Mehlmauer')
|
||||
|
||||
register_options(
|
||||
['--stats', '-s', 'Show WpScan Database statistics.']
|
||||
)
|
||||
end
|
||||
|
||||
def run(options = {})
|
||||
if options[:stats]
|
||||
date_wp = File.mtime(WP_VULNS_FILE)
|
||||
date_plugins = File.mtime(PLUGINS_VULNS_FILE)
|
||||
date_themes = File.mtime(THEMES_VULNS_FILE)
|
||||
date_plugins_full = File.mtime(PLUGINS_FULL_FILE)
|
||||
date_themes_full = File.mtime(THEMES_FULL_FILE)
|
||||
|
||||
puts "WPScan Database Statistics:"
|
||||
puts "---------------------------"
|
||||
puts
|
||||
puts "[#] Total vulnerable versions: #{vuln_core_count}"
|
||||
puts "[#] Total vulnerable plugins: #{vuln_plugin_count}"
|
||||
puts "[#] Total vulnerable themes: #{vuln_theme_count}"
|
||||
puts
|
||||
puts "[#] Total version vulnerabilities: #{version_vulns_count}"
|
||||
puts "[#] Total fixed vulnerabilities: #{fix_version_count}"
|
||||
puts
|
||||
puts "[#] Total plugin vulnerabilities: #{plugin_vulns_count}"
|
||||
puts "[#] Total fixed vulnerabilities: #{fix_plugin_count}"
|
||||
puts
|
||||
puts "[#] Total theme vulnerabilities: #{theme_vulns_count}"
|
||||
puts "[#] Total fixed vulnerabilities: #{fix_theme_count}"
|
||||
puts
|
||||
puts "[#] Total plugins to enumerate: #{total_plugins}"
|
||||
puts "[#] Total themes to enumerate: #{total_themes}"
|
||||
puts
|
||||
puts "[+] WordPress DB modified: #{date_wp.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
puts "[+] Plugins DB modified: #{date_plugins.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
puts "[+] Themes DB modified: #{date_themes.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
puts "[+] Enumeration plugins: #{date_plugins_full.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
puts "[+] Enumeration themes: #{date_themes_full.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
puts
|
||||
puts "[+] Report generated: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
end
|
||||
end
|
||||
|
||||
def vuln_core_count(file=WP_VULNS_FILE)
|
||||
json(file).size
|
||||
end
|
||||
|
||||
def vuln_plugin_count(file=PLUGINS_VULNS_FILE)
|
||||
json(file).size
|
||||
end
|
||||
|
||||
def vuln_theme_count(file=THEMES_VULNS_FILE)
|
||||
json(file).size
|
||||
end
|
||||
|
||||
def version_vulns_count(file=WP_VULNS_FILE)
|
||||
asset_vulns_count(json(file))
|
||||
end
|
||||
|
||||
def fix_version_count(file=WP_VULNS_FILE)
|
||||
asset_fixed_in_count(json(file))
|
||||
end
|
||||
|
||||
def plugin_vulns_count(file=PLUGINS_VULNS_FILE)
|
||||
asset_vulns_count(json(file))
|
||||
end
|
||||
|
||||
def fix_plugin_count(file=PLUGINS_VULNS_FILE)
|
||||
asset_fixed_in_count(json(file))
|
||||
end
|
||||
|
||||
def theme_vulns_count(file=THEMES_VULNS_FILE)
|
||||
asset_vulns_count(json(file))
|
||||
end
|
||||
|
||||
def fix_theme_count(file=THEMES_VULNS_FILE)
|
||||
asset_fixed_in_count(json(file))
|
||||
end
|
||||
|
||||
def total_plugins(file=PLUGINS_FULL_FILE)
|
||||
lines_in_file(file)
|
||||
end
|
||||
|
||||
def total_themes(file=THEMES_FULL_FILE)
|
||||
lines_in_file(file)
|
||||
end
|
||||
|
||||
def lines_in_file(file)
|
||||
IO.readlines(file).size
|
||||
end
|
||||
|
||||
def asset_vulns_count(json)
|
||||
json.map { |asset| asset[asset.keys.inject]['vulnerabilities'].size }.inject(:+)
|
||||
end
|
||||
|
||||
def asset_fixed_in_count(json)
|
||||
json.map { |asset| asset[asset.keys.inject]['vulnerabilities'].map {|a| a['fixed_in'].nil? ? 0 : 1 }.inject(:+) }.inject(:+)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../common/common_helper')
|
||||
|
||||
require_files_from_directory(WPSTOOLS_LIB_DIR)
|
||||
require_files_from_directory(WPSTOOLS_PLUGINS_DIR, '**/*.rb')
|
||||
|
||||
def usage
|
||||
script_name = $0
|
||||
puts
|
||||
puts '-h for further help.'
|
||||
puts
|
||||
puts 'Examples:'
|
||||
puts
|
||||
puts 'Locally scan a wordpress installation for vulnerable files or shells'
|
||||
puts "ruby #{script_name} --check-local-vulnerable-files /var/www/wordpress/"
|
||||
puts
|
||||
puts 'See README for further information.'
|
||||
puts
|
||||
end
|
||||
@@ -64,7 +64,7 @@ describe Browser do
|
||||
|
||||
it 'raises an error' do
|
||||
File.symlink('./testfile', config_file)
|
||||
expect { browser.load_config(config_file) }.to raise_error("[ERROR] Config file is a symlink.")
|
||||
expect { browser.load_config(config_file) }.to raise_error('[ERROR] Config file is a symlink.')
|
||||
File.unlink(config_file)
|
||||
end
|
||||
end
|
||||
@@ -130,7 +130,7 @@ describe Browser do
|
||||
headers: { 'User-Agent' => 'SomeUA' },
|
||||
ssl_verifypeer: false, ssl_verifyhost: 0,
|
||||
cookiejar: cookie_jar, cookiefile: cookie_jar,
|
||||
timeout: 2000, connecttimeout: 1000,
|
||||
timeout: 60, connecttimeout: 10,
|
||||
maxredirs: 3,
|
||||
referer: nil
|
||||
}
|
||||
@@ -147,7 +147,6 @@ describe Browser do
|
||||
@expected = default_expectation
|
||||
end
|
||||
|
||||
|
||||
context 'when @proxy' do
|
||||
let(:proxy) { '127.0.0.1:9050' }
|
||||
let(:proxy_expectation) { default_expectation.merge(proxy: proxy) }
|
||||
@@ -166,11 +165,19 @@ describe Browser do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when @request_timeout' do
|
||||
it 'gives an Integer' do
|
||||
browser.request_timeout = '10'
|
||||
|
||||
@expected = default_expectation.merge(timeout: 10)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when @basic_auth' do
|
||||
it 'appends the basic_auth' do
|
||||
browser.basic_auth = 'user:pass'
|
||||
@expected = default_expectation.merge(
|
||||
headers: default_expectation[:headers].merge('Authorization' => 'Basic '+Base64.encode64('user:pass').chomp)
|
||||
headers: default_expectation[:headers].merge('Authorization' => 'Basic ' + Base64.encode64('user:pass').chomp)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,14 +30,15 @@ describe CacheFileStore do
|
||||
|
||||
describe '#clean' do
|
||||
it "should remove all files from the cache dir (#{@cache_dir}" do
|
||||
# let's create some files into the directory first
|
||||
(0..5).each do |i|
|
||||
File.new(@cache.storage_path + "/file_#{i}.txt", File::CREAT)
|
||||
end
|
||||
|
||||
expect(count_files_in_dir(@cache.storage_path, 'file_*.txt')).to eq 6
|
||||
# clean is executed by other tests before
|
||||
before = count_files_in_dir(@cache.cache_dir)
|
||||
test_dir = File.expand_path("#{@cache.cache_dir}/test")
|
||||
Dir.mkdir test_dir
|
||||
#change the modification date
|
||||
%x[ touch -t 200701310846.26 #{test_dir} ]
|
||||
expect(count_files_in_dir(@cache.cache_dir)).to eq (before + 1)
|
||||
@cache.clean
|
||||
expect(count_files_in_dir(@cache.storage_path)).to eq 0
|
||||
expect(count_files_in_dir(@cache.cache_dir)).to eq before
|
||||
end
|
||||
end
|
||||
|
||||
@@ -91,7 +92,7 @@ describe CacheFileStore do
|
||||
it 'should create a unique storage dir' do
|
||||
storage_dirs = []
|
||||
|
||||
(1..5).each do |i|
|
||||
(1..5).each do |_|
|
||||
storage_dirs << CacheFileStore.new(cache_dir).storage_path
|
||||
end
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ describe WpPlugins do
|
||||
let(:expected) do
|
||||
{
|
||||
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'),
|
||||
WpPlugin.new(uri, name:'plugin-2'),
|
||||
WpPlugin.new(uri, name: 'mr-smith')],
|
||||
|
||||
@@ -13,7 +13,7 @@ describe WpThemes do
|
||||
let(:expected) do
|
||||
{
|
||||
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'),
|
||||
WpTheme.new(uri, name:'42k'),
|
||||
WpTheme.new(uri, name: 'a-ri')],
|
||||
|
||||
@@ -26,10 +26,10 @@ describe 'WpTimthumbs::Detectable' do
|
||||
|
||||
def expected_targets_from_theme(theme_name)
|
||||
expected = []
|
||||
%w{
|
||||
%w(
|
||||
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
|
||||
}.each do |file|
|
||||
).each do |file|
|
||||
path = "$wp-content$/themes/#{theme_name}/#{file}"
|
||||
expected << WpTimthumb.new(uri, path: path)
|
||||
end
|
||||
@@ -46,7 +46,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
after do
|
||||
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
|
||||
|
||||
context 'when an empty file' do
|
||||
@@ -71,7 +71,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
theme = 'hello-world'
|
||||
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
|
||||
|
||||
@@ -81,7 +81,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
after do
|
||||
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
|
||||
|
||||
context 'when no :theme_name' do
|
||||
@@ -101,7 +101,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
end
|
||||
|
||||
context 'when :theme_name' do
|
||||
let(:theme) { 'theme-name'}
|
||||
let(:theme) { 'theme-name' }
|
||||
|
||||
context 'when no :file' do
|
||||
let(:options) { { theme_name: theme } }
|
||||
|
||||
@@ -25,19 +25,19 @@ describe 'WpUsers::Output' do
|
||||
subject.push(@input)
|
||||
subject.flatten!
|
||||
subject.remove_junk_from_display_names
|
||||
expect(subject).to be === @expected
|
||||
expect(subject).to eq @expected
|
||||
end
|
||||
|
||||
it 'should return an empty array' do
|
||||
it 'returns an empty array' do
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return input object' do
|
||||
it 'returns input object' do
|
||||
@input.push(WpUser.new(nil))
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return input object' do
|
||||
it 'returns input object' do
|
||||
@input.push(WpUser.new(''))
|
||||
@expected = @input
|
||||
end
|
||||
@@ -50,23 +50,37 @@ describe 'WpUsers::Output' do
|
||||
@expected.push(WpUser.new('', login: '', id: 2, display_name: 'ijrjd'))
|
||||
end
|
||||
|
||||
it 'should return unmodified input object' do
|
||||
it 'returns unmodified input object' do
|
||||
@input.push(WpUser.new('', login: '', id: 1, display_name: 'lkjh asdfa'))
|
||||
@input.push(WpUser.new('', login: '', id: 2, display_name: 'ijrjd asdf'))
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return input object' do
|
||||
it 'returns input object' do
|
||||
@input.push(WpUser.new('', login: '', id: 1, display_name: 'lkjh asdf'))
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return an empty display_name' do
|
||||
it 'returns an empty display_name' do
|
||||
@input.push(WpUser.new('', login: '', id: 1, display_name: 'lkhj asdf'))
|
||||
@input.push(WpUser.new('', login: '', id: 2, display_name: 'lkhj asdf'))
|
||||
@expected = WpUsers.new(0)
|
||||
@expected.push(WpUser.new('', login: '', id: 1, display_name: ''))
|
||||
@expected.push(WpUser.new('', login: '', id: 2, display_name: ''))
|
||||
end
|
||||
|
||||
context 'when a user has no display_name' do
|
||||
it 'returns an empty display_name' do
|
||||
@input.push(WpUser.new('', login: '', id: 1, display_name: 'lkhj asdf'))
|
||||
@input.push(WpUser.new('', login: '', id: 2, display_name: 'lkhj asdf'))
|
||||
@input.push(WpUser.new('', login: '', id: 3))
|
||||
|
||||
@expected = WpUsers.new(0)
|
||||
|
||||
(1..3).each do |id|
|
||||
@expected.push(WpUser.new('', login: '', id: id, display_name: ''))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,11 +11,11 @@ describe WpItem do
|
||||
end
|
||||
it_behaves_like 'WpItem::Versionable'
|
||||
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(:expected_refs) { {
|
||||
'id' => [2993],
|
||||
'url' => ['Ref 1,Ref 2'],
|
||||
'url' => ['Ref 1', 'Ref 2'],
|
||||
'cve' => ['2011-001'],
|
||||
'secunia' => ['secunia'],
|
||||
'osvdb' => ['osvdb'],
|
||||
@@ -105,11 +105,6 @@ describe WpItem do
|
||||
@expected = 'plugins/readme.txt'
|
||||
end
|
||||
end
|
||||
|
||||
it 'also encodes chars' do
|
||||
@path = 'some dir with spaces'
|
||||
@expected = 'some%20dir%20with%20spaces'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#uri' do
|
||||
|
||||
@@ -5,11 +5,11 @@ require 'spec_helper'
|
||||
describe WpPlugin do
|
||||
it_behaves_like 'WpPlugin::Vulnerable'
|
||||
it_behaves_like 'WpItem::Vulnerable' do
|
||||
let(:options) { { name: 'white-rabbit' } }
|
||||
let(:vulns_file) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins_vulns.json' }
|
||||
let(:options) { { name: 'white-rabbit' } }
|
||||
let(:db_file) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins.json' }
|
||||
let(:expected_refs) { {
|
||||
'id' => [2993],
|
||||
'url' => ['Ref 1,Ref 2'],
|
||||
'url' => ['Ref 1', 'Ref 2'],
|
||||
'cve' => ['2011-001'],
|
||||
'secunia' => ['secunia'],
|
||||
'osvdb' => ['osvdb'],
|
||||
|
||||
@@ -17,10 +17,9 @@ describe 'WpTheme::Findable' do
|
||||
|
||||
wp_theme = WpTheme.send(:find_from_css_link, uri)
|
||||
|
||||
if @expected
|
||||
expect(wp_theme).to be_a WpTheme
|
||||
end
|
||||
expect(wp_theme).to be_a WpTheme if @expected
|
||||
expect(wp_theme).to eq @expected
|
||||
expect(wp_theme.wp_content_dir).to eql 'wp-content' if @expected
|
||||
end
|
||||
|
||||
context 'when theme is not present' do
|
||||
@@ -59,6 +58,13 @@ describe 'WpTheme::Findable' do
|
||||
end
|
||||
end
|
||||
|
||||
# This one might introduce FP btw
|
||||
context 'when leaked from comments' do
|
||||
it 'returns the WpTheme' do
|
||||
@file = 'comments.html'
|
||||
@expected = WpTheme.new(uri, name: 'debug')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '::find_from_wooframework' do
|
||||
@@ -96,7 +102,6 @@ describe 'WpTheme::Findable' do
|
||||
@expected = WpTheme.new(uri, name: 'Editorial', version: '1.3.5')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '::find' do
|
||||
@@ -109,7 +114,6 @@ describe 'WpTheme::Findable' do
|
||||
|
||||
context 'when a method is named s_find_from_s' do
|
||||
it 'does not call it' do
|
||||
|
||||
class WpTheme
|
||||
module Findable
|
||||
extend self
|
||||
@@ -117,7 +121,7 @@ describe 'WpTheme::Findable' do
|
||||
end
|
||||
end
|
||||
|
||||
stub_all_to_nil()
|
||||
stub_all_to_nil
|
||||
|
||||
expect { WpTheme.find(uri) }.to_not raise_error
|
||||
end
|
||||
@@ -125,7 +129,7 @@ describe 'WpTheme::Findable' do
|
||||
|
||||
context 'when the theme is not found' do
|
||||
it 'returns nil' do
|
||||
stub_all_to_nil()
|
||||
stub_all_to_nil
|
||||
|
||||
expect(WpTheme.find(uri)).to be_nil
|
||||
end
|
||||
@@ -133,7 +137,7 @@ describe 'WpTheme::Findable' do
|
||||
|
||||
context 'when the theme is found' do
|
||||
it 'returns it, with the :found_from set' do
|
||||
stub_all_to_nil()
|
||||
stub_all_to_nil
|
||||
stub_request(:get, /.+\/the-oracle\/style.css$/).to_return(status: 200)
|
||||
expected = WpTheme.new(uri, name: 'the-oracle')
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ describe WpTheme do
|
||||
it_behaves_like 'WpTheme::Vulnerable'
|
||||
it_behaves_like 'WpItem::Vulnerable' do
|
||||
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) { {
|
||||
'id' => [2993],
|
||||
'url' => ['Ref 1,Ref 2'],
|
||||
'url' => ['Ref 1', 'Ref 2'],
|
||||
'cve' => ['2011-001'],
|
||||
'secunia' => ['secunia'],
|
||||
'osvdb' => ['osvdb'],
|
||||
|
||||
@@ -65,6 +65,11 @@ describe 'WpVersion::Findable' do
|
||||
@fixture = '/3.5_minified.html'
|
||||
@expected = '3.5'
|
||||
end
|
||||
|
||||
it 'returns 3.5.1' do
|
||||
@fixture = '/3.5.1_mobile.html'
|
||||
@expected = '3.5.1'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -178,7 +183,7 @@ describe 'WpVersion::Findable' do
|
||||
|
||||
context 'when no version found' do
|
||||
it 'returns nil' do
|
||||
stub_all_to_nil()
|
||||
stub_all_to_nil
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
@@ -188,8 +193,8 @@ describe 'WpVersion::Findable' do
|
||||
found_from = method[/^find_from_(.*)/, 1].sub('_', ' ')
|
||||
|
||||
context "when found from #{found_from}" do
|
||||
it "returns the correct WpVersion" do
|
||||
stub_all_to_nil()
|
||||
it 'returns the correct WpVersion' do
|
||||
stub_all_to_nil
|
||||
|
||||
allow(WpVersion).to receive(method).and_return(number)
|
||||
|
||||
|
||||
@@ -4,20 +4,6 @@ require 'spec_helper'
|
||||
|
||||
describe WpVersion do
|
||||
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) }
|
||||
let(:uri) { URI.parse('http://example.com/') }
|
||||
@@ -29,4 +15,12 @@ describe WpVersion do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#all' do
|
||||
let(:versions_file) { File.join(MODELS_FIXTURES, 'wp_version', 'findable', 'advanced_fingerprinting', 'wp_versions.xml') }
|
||||
|
||||
it 'returns the array containign the two versions' do
|
||||
expect(WpVersion.all(versions_file)).to eq ['3.2.1', '3.2']
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -121,4 +121,122 @@ describe 'VersionCompare' do
|
||||
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
|
||||
|
||||
@@ -17,7 +17,7 @@ describe 'WebSite' do
|
||||
)
|
||||
end
|
||||
|
||||
describe "#new" do
|
||||
describe '#new' do
|
||||
its(:url) { is_expected.to be === 'http://example.localhost/' }
|
||||
end
|
||||
|
||||
@@ -68,14 +68,14 @@ describe 'WebSite' do
|
||||
|
||||
describe '#xml_rpc_url' do
|
||||
it 'returns the xmlrpc url' do
|
||||
expect(web_site.xml_rpc_url).to be === "http://example.localhost/xmlrpc.php"
|
||||
expect(web_site.xml_rpc_url).to be === 'http://example.localhost/xmlrpc.php'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_xml_rpc?' do
|
||||
it 'returns true' do
|
||||
stub_request(:get, web_site.xml_rpc_url).
|
||||
to_return(status: 200, body: "XML-RPC server accepts POST requests only")
|
||||
to_return(status: 200, body: 'XML-RPC server accepts POST requests only')
|
||||
|
||||
expect(web_site).to have_xml_rpc
|
||||
end
|
||||
@@ -116,12 +116,24 @@ describe 'WebSite' do
|
||||
|
||||
expect(web_site.redirection).to eql absolute_location
|
||||
end
|
||||
|
||||
context 'when starts with a ?' do
|
||||
it 'returns the absolute URI' do
|
||||
relative_location = '?p=blog'
|
||||
absolute_location = web_site.uri.merge(relative_location).to_s
|
||||
|
||||
stub_request(:get, web_site.url).to_return(status: 301, headers: { location: relative_location })
|
||||
stub_request(:get, absolute_location)
|
||||
|
||||
expect(web_site.redirection).to eql absolute_location
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multiple redirections' do
|
||||
it 'returns the last redirection' do
|
||||
first_redirection = 'www.redirection.com'
|
||||
last_redirection = 'redirection.com'
|
||||
first_redirection = 'http://www.redirection.com'
|
||||
last_redirection = 'http://redirection.com'
|
||||
|
||||
stub_request(:get, web_site.url).to_return(status: 301, headers: { location: first_redirection })
|
||||
stub_request(:get, first_redirection).to_return(status: 302, headers: { location: last_redirection })
|
||||
|
||||
@@ -4,6 +4,7 @@ require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
|
||||
describe WpTarget do
|
||||
subject(:wp_target) { WpTarget.new(target_url, options) }
|
||||
subject(:wp_target_custom) { WpTarget.new(target_url, options_custom) }
|
||||
let(:target_url) { 'http://example.localhost/' }
|
||||
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR }
|
||||
let(:login_url) { wp_target.uri.merge('wp-login.php').to_s }
|
||||
@@ -15,10 +16,17 @@ describe WpTarget do
|
||||
wp_plugins_dir: 'wp-content/plugins'
|
||||
}
|
||||
}
|
||||
let(:options_custom) {
|
||||
{
|
||||
config_file: SPEC_FIXTURES_CONF_DIR + '/browser.conf.json',
|
||||
cache_ttl: 0,
|
||||
wp_content_dir: 'custom-content',
|
||||
wp_plugins_dir: 'custom-content/plugins'
|
||||
}
|
||||
}
|
||||
|
||||
before { Browser::reset }
|
||||
|
||||
it_behaves_like 'WpTarget::Malwares'
|
||||
it_behaves_like 'WpTarget::WpReadme'
|
||||
it_behaves_like 'WpTarget::WpRegistrable'
|
||||
it_behaves_like 'WpTarget::WpConfigBackup'
|
||||
@@ -70,6 +78,11 @@ describe WpTarget do
|
||||
expect(wp_target).to be_wordpress
|
||||
end
|
||||
|
||||
it 'returns true if a custom content directory is detected' do
|
||||
stub_request_to_fixture(url: wp_target_custom.url, fixture: fixtures_dir + '/wp_content_dir/wordpress-3.4.1-custom.htm')
|
||||
expect(wp_target_custom).to be_wordpress
|
||||
end
|
||||
|
||||
it 'returns true if the xmlrpc is found' do
|
||||
stub_request(:get, wp_target.xml_rpc_url).
|
||||
to_return(status: 200, body: File.new(fixtures_dir + '/xmlrpc.php'))
|
||||
@@ -136,7 +149,7 @@ describe WpTarget do
|
||||
|
||||
after :each do
|
||||
allow(wp_target).to receive_messages(wp_content_dir: 'wp-content')
|
||||
stub_request_to_fixture(url: wp_target.debug_log_url(), fixture: @fixture)
|
||||
stub_request_to_fixture(url: wp_target.debug_log_url, fixture: @fixture)
|
||||
expect(wp_target.has_debug_log?).to be === @expected
|
||||
end
|
||||
|
||||
|
||||
@@ -32,6 +32,11 @@ describe 'WpscanOptions' do
|
||||
@wpscan_options.url = url
|
||||
expect(@wpscan_options.url).to be === url
|
||||
end
|
||||
|
||||
it 'should encode IDN' do
|
||||
@wpscan_options.url = 'http://пример.испытание/'
|
||||
expect(@wpscan_options.url).to be === 'http://xn--e1afmkfd.xn--80akhbyknj4f/'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#threads=' do
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../wpstools_helper')
|
||||
|
||||
describe 'StatsPlugin' do
|
||||
subject(:stats) { StatsPlugin.new }
|
||||
let(:plugins_vulns) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins_vulns.json' }
|
||||
let(:themes_vulns) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' }
|
||||
let(:plugins_file) { COLLECTIONS_FIXTURES + '/wp_plugins/detectable/targets.txt' }
|
||||
let(:themes_file) { COLLECTIONS_FIXTURES + '/wp_themes/detectable/targets.txt'}
|
||||
|
||||
describe '#vuln_plugin_count' do
|
||||
it 'returns the correct number' do
|
||||
expect(stats.vuln_plugin_count(plugins_vulns)).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
describe '#vuln_theme_count' do
|
||||
it 'returns the correct number' do
|
||||
expect(stats.vuln_theme_count(themes_vulns)).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
describe '#plugin_vulns_count' do
|
||||
it 'returns the correct number' do
|
||||
expect(stats.plugin_vulns_count(plugins_vulns)).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
describe '#theme_vulns_count' do
|
||||
it 'returns the correct number' do
|
||||
expect(stats.theme_vulns_count(themes_vulns)).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
describe '#total_plugins' do
|
||||
it 'returns the correct numer' do
|
||||
expect(stats.total_plugins(plugins_file)).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
describe '#total_themes' do
|
||||
it 'returns the correct numer' do
|
||||
expect(stats.total_themes(themes_file)).to eq 3
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,4 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'spec_helper'
|
||||
require WPSTOOLS_LIB_DIR + '/wpstools_helper'
|
||||
@@ -1,58 +1,64 @@
|
||||
[
|
||||
{
|
||||
"mr-smith":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"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",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
{
|
||||
"mr-smith": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||
"references": {
|
||||
"url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"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",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"title":"Potential Authentication Cookie Forgery",
|
||||
"references": {
|
||||
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
|
||||
"osvdb":"105620",
|
||||
"cve":"2014-0166",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"cve":"2014-0166"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references": {
|
||||
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"osvdb":"105630",
|
||||
"cve":"2014-0165",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"cve":"2014-0165"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"references": {
|
||||
"osvdb":"105622",
|
||||
"secunia":"57769",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"secunia":"57769"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"neo":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references":"http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
"neo": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references": {
|
||||
"url": "http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,58 +1,64 @@
|
||||
[
|
||||
{
|
||||
"mr-smith":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"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",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
{
|
||||
"mr-smith": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||
"references": {
|
||||
"url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"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",
|
||||
"osvdb":"105620",
|
||||
"cve":"2014-0166",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"title":"Potential Authentication Cookie Forgery",
|
||||
"references": {
|
||||
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"osvdb":"105620",
|
||||
"cve":"2014-0166",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references": {
|
||||
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"osvdb":"105630",
|
||||
"cve":"2014-0165",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"cve":"2014-0165"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"references": {
|
||||
"osvdb":"105622",
|
||||
"secunia":"57769",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"secunia":"57769"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"neo":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references":"http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
"neo": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references": {
|
||||
"url": "http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,58 +1,65 @@
|
||||
[
|
||||
{
|
||||
"shopperpress":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"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",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
{
|
||||
"shopperpress": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||
"references": {
|
||||
"url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"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",
|
||||
"osvdb":"105620",
|
||||
"cve":"2014-0166",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"title":"Potential Authentication Cookie Forgery",
|
||||
"references": {
|
||||
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
|
||||
"osvdb":"105620",
|
||||
"cve":"2014-0166"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"osvdb":"105630",
|
||||
"cve":"2014-0165",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references": {
|
||||
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"osvdb":"105630",
|
||||
"cve":"2014-0165"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"references": {
|
||||
"osvdb":"105622",
|
||||
"secunia":"57769",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"secunia":"57769"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"webfolio":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references":"http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
"webfolio": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references": {
|
||||
"url": "http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"id": "3911",
|
||||
"title": "Vuln Title",
|
||||
"url": "Ref 1,Ref 2",
|
||||
"secunia": "secunia",
|
||||
"osvdb": "osvdb",
|
||||
"cve": "2011-001",
|
||||
"metasploit": "exploit/ex1",
|
||||
"exploitdb": "exploitdb",
|
||||
"references":{
|
||||
"url": "Ref 1,Ref 2",
|
||||
"secunia": "secunia",
|
||||
"osvdb": "osvdb",
|
||||
"cve": "2011-001",
|
||||
"metasploit": "exploit/ex1",
|
||||
"exploitdb": "exploitdb"
|
||||
},
|
||||
"created_at": "2014-07-28T12:10:45.000Z",
|
||||
"updated_at": "2014-07-28T12:10:45.000Z",
|
||||
"type": "CSRF",
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
=== A Capture Contact Form (and tab) by AWebVoice.com ===
|
||||
Contributors: AWebVoice
|
||||
Donate link:
|
||||
Tags: contact form, web form, capture contact form, response forms, captcha form, get response, contact me, contact form 7
|
||||
Requires at least: 2.0.2
|
||||
Tested up to: 3.1.1
|
||||
Stable tag: trunk
|
||||
|
||||
Get a contact form and a contact button. Capture your visitors and turn them into customers!
|
||||
|
||||
== Description ==
|
||||
|
||||
A fully customizable contact form on your WordPress blog. And a contact tab to increase customer interaction. Join our fast growing users base who have chosen AWebVoice as their contact form of choice!
|
||||
|
||||
= Get your FREE Contact Form plugin for Wordpress =
|
||||
|
||||
Our Contact Form plugin is full of features that no other wordpress contact form can offer. See for yourself, below are a few of the features we offer:
|
||||
|
||||
* Add a customized contact form to your Wordpress blog which includes a message, contact's email, name, phone number and more!
|
||||
* Create and customize your contact form settings right from within your Wordpress Admin Panel
|
||||
* Include your logo, business contact info, even social links right on your contact form
|
||||
* Notifications: Get a contacts message notifications to your inbox!
|
||||
* Setup multiple autoresponder for your contact form so your visitors get an instant message from you
|
||||
* Take your autoresponders to the next level, and ask your contact to join your email list.
|
||||
* Setup a custom success message or URL for your contact form
|
||||
* Each form has built in ROI tracking
|
||||
* Increases conversion: Include a custom “Contact†tab to the side of your wordpress blog that pops up your contact form.
|
||||
* ...these features will always be free, but go to the next level and get many more features for your contact form!
|
||||
|
||||
= More than a Contact Form =
|
||||
|
||||
Behind the AWebVoice wordpress plugin contact form is a suite of online tools to effectively manage all of your leads, contacts, and marketing ROI needs. As leads come in from your contact form, those contacts are automatically added to your online contacts database. From there, access information about each contact, send emails to each contact, and fully track, organize, and manage your communications. AWebVoice.com is designed to scale from single person offices to Fortune 500 companies -- and it is created on the very largest online database has to offer.
|
||||
|
||||
|
||||
= And it is easy to get start =
|
||||
|
||||
AWebVoice.com’s contact form is FREE and is the easiest wordpress contact form to use. We think you will agree, give it a try sign up now:
|
||||
www.awebvoice.com.
|
||||
|
||||
== Installation ==
|
||||
|
||||
= Option 1: Install the plugin via your Wordpress admin panel =
|
||||
1. Login to your Wordpress system which should take you to the Dashboard of your Wordpress account.
|
||||
1. Click the "Plugins" menu on the left menu bar. The choose "Add New".
|
||||
1. Search for "AWebVoice", "Contact " or "contact form".
|
||||
1. Click Install Now, the "Contact Form" by AWebVoice.com.
|
||||
1. After installation has finished, you need to activate the plugin.
|
||||
1. You should see the AWebVoice plugin listed in the available plugins. Click the "Activate" link.
|
||||
1. Next, click the "Settings" menu on the left menu bar and choose "AWebVoice Form"
|
||||
1. A form will be presented. Enter the e-mail address to receive contact form messages, and click "Create Account".
|
||||
1. Your AWebVoice tab-button and contact form have been installed and are working on your blog.
|
||||
|
||||
= Option 2: Manual plugin installation =
|
||||
1. Click on the red "Download Version x.x" button on the right side of this page.
|
||||
1. After the download has finished, extract the files.
|
||||
1. Upload the "awebvoivce" folder to your server in "/wp-content/plugins" directory
|
||||
1. Login to you Wordpress system which should take you to the admin panel or Dashboard. Click on "Plugins" menu.
|
||||
1. You should see the AWebVoice plugin listed in the available plugins. Click the "Activate" link.
|
||||
1. Then click on the "Settings" menu in the left side menu bar. Choose "AWebVoice Form"
|
||||
1. A form will be presented. Enter the e-mail address to receive contact form messages, and click "Create Account".
|
||||
1. Your AWebVoice tab-button and contact form have been installed and are working on your blog.
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= What is Contact Form by AWebVoice.com? =
|
||||
Unlike other contact form providers like contact form 7, you will never have to touch a line of code to create, edit, or modify your contact form. Just install the Awebvoice.com contact form plugin, choose the email address you want your form submissions to go to, and that is it. Your new contacts are delivered to the specified email address AND stored in an online database under your name. Your contacts are yours and not shared with others. How you use your new contacts is up to you.
|
||||
|
||||
= Can I customize my contact form? =
|
||||
Yes. Editing a form is quick and easy. In your WordPress administration page, click on Settings. Click on the AWebVoice plugin and click the modify button to edit your contact form. (You may have to login first to AWebVoice.com) With the AWebVoice contact form editor, you can create a beautiful and customized contact forms, including your logo, your address and phone, and more.
|
||||
|
||||
= What is the AWebVoice tab-button? =
|
||||
The tab-button is a button that sits on the side of your blog and maintains position even as your visitors scroll. Your potential new contacts or existing contacts are only one click away from sending you a message! When the button is clicked, your contact form pops right up in a nice modal window, darkening the rest of the screen and focusing the user on completing your contact form. This AWebVoice tab-button and contact form combination has been proven improve conversions on a website by over 45%.
|
||||
|
||||
= What other unique features do you offer? =
|
||||
Many more features. AWebVoice is a full featured email, newsletter, coupon marketing system. The tab-button and contact form will always be free. But if you need more, such as self-managed email lists, we have it. We also have ROI reports so you can track your contact form and blog success rate. The AWebVoice Email/Newsletter management system is a subscription based system. We want to help you get started, and once the task of managing your contact list get too large, we are there to support your efforts. AWebVoice is the most effective marketing tool a small or large business can have.
|
||||
|
||||
Get your free AWebVoice contact form today and start watching your leads grow!
|
||||
|
||||
== Screenshots ==
|
||||
|
||||
1. Contact Form Admin Panel
|
||||
2. A Contact Form
|
||||
3. Contact Tab-Button on the Blog (click opens contact form)
|
||||
|
||||
== Change Log ==
|
||||
|
||||
= Coming Soon =
|
||||
* Contact Form: More custom from fields for your contact form
|
||||
* Contact Form: More languages for button and contact form
|
||||
|
||||
= 3.1 =
|
||||
* Initial WordPress Release.
|
||||
* Analytics included in Contact Form
|
||||
* Custom form fields for your contact form
|
||||
* Form title is editable
|
||||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 1.0 =
|
||||
Upgrade notices when available will be described in this section.
|
||||
|
||||
= Languages =
|
||||
* English: Available contact form and button
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user