Compare commits
236 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bff063805 | ||
|
|
53d9956829 | ||
|
|
6e98678c3c | ||
|
|
f0f21f5ac2 | ||
|
|
aa233b1c4d | ||
|
|
93f9123f45 | ||
|
|
5c710d88e4 | ||
|
|
ded70ff743 | ||
|
|
9df7443aa4 | ||
|
|
8362975691 | ||
|
|
49771419ae | ||
|
|
d344f84824 | ||
|
|
89c0b8d4d0 | ||
|
|
3c74ee8d97 | ||
|
|
785c6efa5b | ||
|
|
4e2bf5322e | ||
|
|
54ed148c87 | ||
|
|
b08e298eba | ||
|
|
89e2088357 | ||
|
|
f3cc35bd74 | ||
|
|
a007d283e5 | ||
|
|
70902aa013 | ||
|
|
91151fc53b | ||
|
|
d4ee82dac5 | ||
|
|
88d3c26113 | ||
|
|
054a4ee6aa | ||
|
|
c291022753 | ||
|
|
2fc488b602 | ||
|
|
009ddd690e | ||
|
|
88b5cd8751 | ||
|
|
cfd19d02b1 | ||
|
|
19ce30d862 | ||
|
|
c6df6e0e89 | ||
|
|
e942a5bcf6 | ||
|
|
c0f5163d07 | ||
|
|
f5aa9f117f | ||
|
|
498d93377d | ||
|
|
52242e706b | ||
|
|
22d69a1bf9 | ||
|
|
0b1fa13696 | ||
|
|
19b15b5327 | ||
|
|
e63e96f5ed | ||
|
|
e8ac8f26a7 | ||
|
|
13e4327de4 | ||
|
|
c22a1ed12a | ||
|
|
be5662b5f1 | ||
|
|
6e840ca920 | ||
|
|
8492190f4c | ||
|
|
93ab6ee2a0 | ||
|
|
7075e01886 | ||
|
|
436a83434c | ||
|
|
d270391b56 | ||
|
|
7f2762eb6f | ||
|
|
2cc5bb0311 | ||
|
|
d697127261 | ||
|
|
825523a851 | ||
|
|
0f3f9cac33 | ||
|
|
f9b545b100 | ||
|
|
943bfc39b3 | ||
|
|
b1a8f445c6 | ||
|
|
5435df4345 | ||
|
|
8e9d29e94f | ||
|
|
1afa761f09 | ||
|
|
d626913ce9 | ||
|
|
9c52e4a5ee | ||
|
|
72c2c1992b | ||
|
|
e1b4b5e8e5 | ||
|
|
0243522854 | ||
|
|
5118c68f45 | ||
|
|
442884b5c5 | ||
|
|
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 |
18
.dockerignore
Normal file
18
.dockerignore
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
git/
|
||||||
|
bundle/
|
||||||
|
.idea/
|
||||||
|
.yardoc/
|
||||||
|
cache/
|
||||||
|
coverage/
|
||||||
|
spec/
|
||||||
|
dev/
|
||||||
|
.*
|
||||||
|
**/*.md
|
||||||
|
*.md
|
||||||
|
Dockerfile
|
||||||
|
**/*.orig
|
||||||
|
*.orig
|
||||||
|
CREDITS
|
||||||
|
data.zip
|
||||||
|
DISCLAIMER.txt
|
||||||
|
example.conf.json
|
||||||
@@ -1 +1 @@
|
|||||||
2.2.1
|
2.3.1
|
||||||
|
|||||||
30
.travis.yml
30
.travis.yml
@@ -1,20 +1,24 @@
|
|||||||
language: ruby
|
language: ruby
|
||||||
|
sudo: false
|
||||||
|
cache: bundler
|
||||||
rvm:
|
rvm:
|
||||||
- 1.9.2
|
- 2.1.9
|
||||||
- 1.9.3
|
|
||||||
- 2.0.0
|
|
||||||
- 2.1.0
|
|
||||||
- 2.1.1
|
|
||||||
- 2.1.2
|
|
||||||
- 2.1.3
|
|
||||||
- 2.1.4
|
|
||||||
- 2.1.5
|
|
||||||
- 2.2.0
|
- 2.2.0
|
||||||
- 2.2.1
|
- 2.2.1
|
||||||
|
- 2.2.2
|
||||||
|
- 2.2.3
|
||||||
|
- 2.2.4
|
||||||
|
- 2.3.0
|
||||||
|
- 2.3.1
|
||||||
|
before_install:
|
||||||
|
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||||
|
before_script:
|
||||||
|
- "unzip -o $TRAVIS_BUILD_DIR/data.zip -d $TRAVIS_BUILD_DIR"
|
||||||
script: bundle exec rspec
|
script: bundle exec rspec
|
||||||
notifications:
|
notifications:
|
||||||
email:
|
email:
|
||||||
- wpscanteam@gmail.com
|
- team@wpscan.org
|
||||||
matrix:
|
# do not build gh-pages branch
|
||||||
allow_failures:
|
branches:
|
||||||
- rvm: 1.9.2
|
except:
|
||||||
|
- gh-pages
|
||||||
|
|||||||
132
CHANGELOG.md
132
CHANGELOG.md
@@ -1,6 +1,131 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
## Master
|
## Master
|
||||||
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.7...master)
|
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.9.2...master)
|
||||||
|
|
||||||
|
## Version 2.9.2
|
||||||
|
Released: 2016-11-15
|
||||||
|
|
||||||
|
* Fixed error when detecting plugins with UTF-8 characters
|
||||||
|
* Use all possible finders to verify a detected version
|
||||||
|
* Fix error when detecting a WordPress version not in our database
|
||||||
|
* Added some additional clarification on error messages
|
||||||
|
* Upgrade terminal-table gem
|
||||||
|
* Add --cache-dir option
|
||||||
|
* Add --disable-tls-checks options
|
||||||
|
* Improve/add additional plugin passive detections
|
||||||
|
* Remove scripts when calculating page hashes
|
||||||
|
* Many other small bug fixes.
|
||||||
|
|
||||||
|
WPScan Database Statistics:
|
||||||
|
* Total tracked wordpresses: 194
|
||||||
|
* Total tracked plugins: 63703
|
||||||
|
* Total tracked themes: 13835
|
||||||
|
* Total vulnerable wordpresses: 177
|
||||||
|
* Total vulnerable plugins: 1382
|
||||||
|
* Total vulnerable themes: 379
|
||||||
|
* Total wordpress vulnerabilities: 2617
|
||||||
|
* Total plugin vulnerabilities: 2190
|
||||||
|
* Total theme vulnerabilities: 452
|
||||||
|
|
||||||
|
## 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
|
## Version 2.7
|
||||||
Released: 2015-03-16
|
Released: 2015-03-16
|
||||||
@@ -33,7 +158,7 @@ Fixed issues
|
|||||||
* Fix #746 - Add a global counter for all active requests to server.
|
* Fix #746 - Add a global counter for all active requests to server.
|
||||||
* Fix #747 - Add 'security-protection' plugin to wp_login_protection module
|
* 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 #753 - undefined method `round' for "10":String for request or connect timeouts
|
||||||
* Fix #760 - typhoeus issue (infinite loop)
|
* Fix #760 - typhoeus issue (infinite loop)
|
||||||
|
|
||||||
WPScan Database Statistics:
|
WPScan Database Statistics:
|
||||||
* Total vulnerable versions: 89
|
* Total vulnerable versions: 89
|
||||||
@@ -54,7 +179,7 @@ New
|
|||||||
* Add Sucuri sponsor to banner
|
* Add Sucuri sponsor to banner
|
||||||
* Add protocol to sucuri url in banner
|
* Add protocol to sucuri url in banner
|
||||||
* Add response code to proxy error output
|
* Add response code to proxy error output
|
||||||
* Add a statement about mendatory newlines at the end of list
|
* Add a statement about mandatory newlines at the end of list
|
||||||
* Give warning if default username 'admin' is still used
|
* Give warning if default username 'admin' is still used
|
||||||
* License amendment to make it more clear about value added usage
|
* License amendment to make it more clear about value added usage
|
||||||
|
|
||||||
@@ -410,4 +535,3 @@ Fixed issues
|
|||||||
|
|
||||||
## Version 2.1
|
## Version 2.1
|
||||||
Released 2013-3-4
|
Released 2013-3-4
|
||||||
|
|
||||||
|
|||||||
2
CREDITS
2
CREDITS
@@ -1,6 +1,6 @@
|
|||||||
**CREDITS**
|
**CREDITS**
|
||||||
|
|
||||||
This file is used to state the individual WPScan Team members (core developers) and give credit to WPScan's other contributors. If you feel your name should be in here email wpscanteam@gmail.com.
|
This file is used to state the individual WPScan Team members (core developers) and give credit to WPScan's other contributors. If you feel your name should be in here email team@wpscan.org.
|
||||||
|
|
||||||
*WPScan Team*
|
*WPScan Team*
|
||||||
|
|
||||||
|
|||||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
FROM ruby:2.3-slim
|
||||||
|
MAINTAINER WPScan Team <team@wpscan.org>
|
||||||
|
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get --no-install-recommends -qq -y install curl git ca-certificates openssl libcurl4-openssl-dev libxml2 libxml2-dev libxslt1-dev build-essential procps
|
||||||
|
|
||||||
|
RUN useradd -d /wpscan wpscan
|
||||||
|
RUN echo "gem: --no-ri --no-rdoc" > /etc/gemrc
|
||||||
|
RUN mkdir /wpscan
|
||||||
|
|
||||||
|
COPY . /wpscan
|
||||||
|
|
||||||
|
WORKDIR /wpscan
|
||||||
|
|
||||||
|
RUN bundle install --without test
|
||||||
|
RUN chown -R wpscan:wpscan /wpscan
|
||||||
|
|
||||||
|
USER wpscan
|
||||||
|
RUN /wpscan/wpscan.rb --update --verbose --no-color
|
||||||
|
|
||||||
|
ENTRYPOINT ["/wpscan/wpscan.rb"]
|
||||||
|
CMD ["--help"]
|
||||||
10
Gemfile
10
Gemfile
@@ -1,15 +1,15 @@
|
|||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
gem 'typhoeus', '~>0.7.0'
|
gem 'typhoeus', '>=1.0.0'
|
||||||
gem 'nokogiri'
|
gem 'nokogiri', '>=1.6.7.2'
|
||||||
gem 'addressable'
|
gem 'addressable'
|
||||||
gem 'json'
|
gem 'yajl-ruby' # Better JSON parser regarding memory usage
|
||||||
gem 'terminal-table'
|
gem 'terminal-table', '>=1.6.0'
|
||||||
gem 'ruby-progressbar', '>=1.6.0'
|
gem 'ruby-progressbar', '>=1.6.0'
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'webmock', '>=1.17.2'
|
gem 'webmock', '>=1.17.2'
|
||||||
gem 'simplecov'
|
gem 'simplecov'
|
||||||
gem 'rspec', '>=3.0'
|
gem 'rspec', '>=3.3.0'
|
||||||
gem 'rspec-its'
|
gem 'rspec-its'
|
||||||
end
|
end
|
||||||
|
|||||||
8
LICENSE
8
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
WPScan Public Source License
|
WPScan Public Source License
|
||||||
|
|
||||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2015 WPScan Team.
|
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2016 WPScan Team.
|
||||||
|
|
||||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ Example cases which do not require a commercial license, and thus fall under the
|
|||||||
- Using WPScan to test your own systems.
|
- Using WPScan to test your own systems.
|
||||||
- Any non-commercial use of WPScan.
|
- Any non-commercial use of WPScan.
|
||||||
|
|
||||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - wpscanteam@gmail.com.
|
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
||||||
|
|
||||||
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
||||||
|
|
||||||
@@ -68,3 +68,7 @@ To the extent permitted under Law, WPScan is provided under an AS-IS basis. The
|
|||||||
10. Disclaimer
|
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.
|
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.
|
||||||
|
|
||||||
|
11. Trademark
|
||||||
|
|
||||||
|
The "wpscan" term is a registered trademark. This License does not grant the use of the "wpscan" trademark or the use of the WPScan logo.
|
||||||
|
|||||||
252
README.md
252
README.md
@@ -4,16 +4,17 @@
|
|||||||
[](https://travis-ci.org/wpscanteam/wpscan)
|
[](https://travis-ci.org/wpscanteam/wpscan)
|
||||||
[](https://codeclimate.com/github/wpscanteam/wpscan)
|
[](https://codeclimate.com/github/wpscanteam/wpscan)
|
||||||
[](https://gemnasium.com/wpscanteam/wpscan)
|
[](https://gemnasium.com/wpscanteam/wpscan)
|
||||||
|
[](https://hub.docker.com/r/wpscanteam/wpscan/)
|
||||||
|
|
||||||
#### LICENSE
|
# LICENSE
|
||||||
|
|
||||||
#### WPScan Public Source License
|
## WPScan Public Source License
|
||||||
|
|
||||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2015 WPScan Team.
|
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2016 WPScan Team.
|
||||||
|
|
||||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
||||||
|
|
||||||
##### 1. Definitions
|
### 1. Definitions
|
||||||
|
|
||||||
1.1 "License" means this document.
|
1.1 "License" means this document.
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ Cases that include commercialization of WPScan require a commercial, non-free li
|
|||||||
|
|
||||||
1.3 "WPScan Team" means WPScan’s core developers, an updated list of whom can be found within the CREDITS file.
|
1.3 "WPScan Team" means WPScan’s core developers, an updated list of whom can be found within the CREDITS file.
|
||||||
|
|
||||||
##### 2. Commercialization
|
### 2. Commercialization
|
||||||
|
|
||||||
A commercial use is one intended for commercial advantage or monetary compensation.
|
A commercial use is one intended for commercial advantage or monetary compensation.
|
||||||
|
|
||||||
@@ -38,13 +39,13 @@ Example cases which do not require a commercial license, and thus fall under the
|
|||||||
- Using WPScan to test your own systems.
|
- Using WPScan to test your own systems.
|
||||||
- Any non-commercial use of WPScan.
|
- Any non-commercial use of WPScan.
|
||||||
|
|
||||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - wpscanteam@gmail.com.
|
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
||||||
|
|
||||||
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
||||||
|
|
||||||
Free-use Terms and Conditions;
|
Free-use Terms and Conditions;
|
||||||
|
|
||||||
##### 3. Redistribution
|
### 3. Redistribution
|
||||||
|
|
||||||
Redistribution is permitted under the following conditions:
|
Redistribution is permitted under the following conditions:
|
||||||
|
|
||||||
@@ -52,35 +53,39 @@ Redistribution is permitted under the following conditions:
|
|||||||
- Unmodified Copyright notices are provided with WPScan.
|
- Unmodified Copyright notices are provided with WPScan.
|
||||||
- Does not conflict with the commercialization clause.
|
- Does not conflict with the commercialization clause.
|
||||||
|
|
||||||
##### 4. Copying
|
### 4. Copying
|
||||||
|
|
||||||
Copying is permitted so long as it does not conflict with the Redistribution clause.
|
Copying is permitted so long as it does not conflict with the Redistribution clause.
|
||||||
|
|
||||||
##### 5. Modification
|
### 5. Modification
|
||||||
|
|
||||||
Modification is permitted so long as it does not conflict with the Redistribution clause.
|
Modification is permitted so long as it does not conflict with the Redistribution clause.
|
||||||
|
|
||||||
##### 6. Contributions
|
### 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.
|
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
|
### 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.
|
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
|
### 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.
|
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
|
### 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.
|
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
|
### 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.
|
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
|
### 11. Trademark
|
||||||
|
|
||||||
|
The "wpscan" term is a registered trademark. This License does not grant the use of the "wpscan" trademark or the use of the WPScan logo.
|
||||||
|
|
||||||
|
# INSTALL
|
||||||
|
|
||||||
WPScan comes pre-installed on the following Linux distributions:
|
WPScan comes pre-installed on the following Linux distributions:
|
||||||
|
|
||||||
@@ -88,82 +93,78 @@ WPScan comes pre-installed on the following Linux distributions:
|
|||||||
- [Kali Linux](http://www.kali.org/)
|
- [Kali Linux](http://www.kali.org/)
|
||||||
- [Pentoo](http://www.pentoo.ch/)
|
- [Pentoo](http://www.pentoo.ch/)
|
||||||
- [SamuraiWTF](http://samurai.inguardians.com/)
|
- [SamuraiWTF](http://samurai.inguardians.com/)
|
||||||
- [ArchAssault](https://archassault.org/)
|
- [BlackArch](http://blackarch.org/)
|
||||||
|
|
||||||
Prerequisites:
|
Windows is not supported
|
||||||
|
|
||||||
- Ruby >= 1.9.2 - Recommended: 2.2.1
|
## Prerequisites
|
||||||
|
|
||||||
|
- Ruby >= 2.1.9 - Recommended: 2.3.1
|
||||||
- Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
|
- Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
|
||||||
- RubyGems - Recommended: latest
|
- RubyGems - Recommended: latest
|
||||||
- Git
|
- Git
|
||||||
|
|
||||||
Windows is not supported.
|
### Installing dependencies on Ubuntu
|
||||||
If installed from Github update the code base with ```git pull```. The databases are updated with ```wpscan.rb --update```.
|
|
||||||
|
|
||||||
####Installing on Ubuntu:
|
sudo apt-get install libcurl4-openssl-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential libgmp-dev zlib1g-dev
|
||||||
|
|
||||||
Before Ubuntu 14.04:
|
### Installing dependencies on Debian
|
||||||
|
|
||||||
sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
|
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 && bundle install --without test
|
|
||||||
|
|
||||||
From Ubuntu 14.04:
|
### Installing dependencies on Fedora
|
||||||
|
|
||||||
sudo apt-get install libcurl4-gnutls-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential
|
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
|
|
||||||
|
|
||||||
####Installing on Debian:
|
### Installing dependencies on Arch Linux
|
||||||
|
|
||||||
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 patch
|
|
||||||
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 ruby
|
||||||
pacman -Syu libyaml
|
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 OSX:
|
### Installing dependencies on Mac OSX
|
||||||
|
|
||||||
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](http://stackoverflow.com/questions/17775115/cant-setup-ruby-environment-installing-fii-gem-error)
|
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](http://stackoverflow.com/questions/17775115/cant-setup-ruby-environment-installing-fii-gem-error)
|
||||||
|
|
||||||
git clone https://github.com/wpscanteam/wpscan.git
|
## Installing with RVM (recommended)
|
||||||
cd wpscan
|
|
||||||
sudo gem install bundler && sudo bundle install --without test
|
|
||||||
|
|
||||||
####Installing with RVM:
|
If you are using GNOME Terminal, there are some steps required before executing the commands. See here for more information:
|
||||||
|
https://rvm.io/integration/gnome-terminal#integrating-rvm-with-gnome-terminal
|
||||||
|
|
||||||
|
# Install all prerequisites for your OS (look above)
|
||||||
cd ~
|
cd ~
|
||||||
|
curl -sSL https://rvm.io/mpapis.asc | gpg --import -
|
||||||
curl -sSL https://get.rvm.io | bash -s stable
|
curl -sSL https://get.rvm.io | bash -s stable
|
||||||
source ~/.rvm/scripts/rvm
|
source ~/.rvm/scripts/rvm
|
||||||
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
|
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
|
||||||
rvm install 2.2.1
|
rvm install 2.3.1
|
||||||
rvm use 2.2.1 --default
|
rvm use 2.3.1 --default
|
||||||
echo "gem: --no-ri --no-rdoc" > ~/.gemrc
|
echo "gem: --no-ri --no-rdoc" > ~/.gemrc
|
||||||
gem install bundler
|
gem install bundler
|
||||||
git clone https://github.com/wpscanteam/wpscan.git
|
git clone https://github.com/wpscanteam/wpscan.git
|
||||||
cd wpscan
|
cd wpscan
|
||||||
|
gem install bundler
|
||||||
bundle install --without test
|
bundle install --without test
|
||||||
|
|
||||||
#### KNOWN ISSUES
|
## Installing manually (not recommended)
|
||||||
|
|
||||||
|
git clone https://github.com/wpscanteam/wpscan.git
|
||||||
|
cd wpscan
|
||||||
|
sudo gem install bundler && bundle install --without test
|
||||||
|
|
||||||
|
# DOCKER
|
||||||
|
Pull the repo with `docker pull wpscanteam/wpscan`
|
||||||
|
|
||||||
|
## Start WPScan
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --rm wpscanteam/wpscan -u http://yourblog.com [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
For the available Options, please see https://github.com/wpscanteam/wpscan#wpscan-arguments
|
||||||
|
|
||||||
|
Published on https://hub.docker.com/r/wpscanteam/wpscan/
|
||||||
|
|
||||||
|
# KNOWN ISSUES
|
||||||
|
|
||||||
- Typhoeus segmentation fault
|
- Typhoeus segmentation fault
|
||||||
|
|
||||||
@@ -190,7 +191,7 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
|||||||
|
|
||||||
Then, open the directory of the readline gem (you have to locate it)
|
Then, open the directory of the readline gem (you have to locate it)
|
||||||
|
|
||||||
cd ~/.rvm/src/ruby-1.9.2-p180/ext/readline
|
cd ~/.rvm/src/ruby-XXXX/ext/readline
|
||||||
ruby extconf.rb
|
ruby extconf.rb
|
||||||
make
|
make
|
||||||
make install
|
make install
|
||||||
@@ -206,15 +207,12 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
|||||||
|
|
||||||
See [https://github.com/wpscanteam/wpscan/issues/148](https://github.com/wpscanteam/wpscan/issues/148)
|
See [https://github.com/wpscanteam/wpscan/issues/148](https://github.com/wpscanteam/wpscan/issues/148)
|
||||||
|
|
||||||
#### WPSCAN ARGUMENTS
|
# WPSCAN ARGUMENTS
|
||||||
|
|
||||||
--update Update the databases.
|
--update Update the database to the latest version.
|
||||||
|
--url | -u <target url> The WordPress URL/domain to scan.
|
||||||
--url | -u <target url> The WordPress URL/domain to scan.
|
--force | -f Forces WPScan to not check if the remote site is running WordPress.
|
||||||
|
--enumerate | -e [option(s)] Enumeration.
|
||||||
--force | -f Forces WPScan to not check if the remote site is running WordPress.
|
|
||||||
|
|
||||||
--enumerate | -e [option(s)] Enumeration.
|
|
||||||
option :
|
option :
|
||||||
u usernames from id 1 to 10
|
u usernames from id 1 to 10
|
||||||
u[10-20] usernames from id 10 to 20 (you must write [] chars)
|
u[10-20] usernames from id 10 to 20 (you must write [] chars)
|
||||||
@@ -228,55 +226,44 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
|||||||
Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
|
Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
|
||||||
If no option is supplied, the default is "vt,tt,u,vp"
|
If no option is supplied, the default is "vt,tt,u,vp"
|
||||||
|
|
||||||
--exclude-content-based "<regexp or string>" Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied
|
--exclude-content-based "<regexp or string>"
|
||||||
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double)
|
Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied.
|
||||||
|
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).
|
||||||
|
--config-file | -c <config file> Use the specified config file, see the example.conf.json.
|
||||||
|
--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.
|
||||||
|
--log Creates a log.txt file with WPScan's output.
|
||||||
|
--no-banner Prevents the WPScan banner from being displayed.
|
||||||
|
--disable-accept-header Prevents WPScan sending the Accept HTTP header.
|
||||||
|
--disable-referer Prevents setting the Referer header.
|
||||||
|
--disable-tls-checks Disables SSL/TLS certificate verification.
|
||||||
|
--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specify 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.
|
||||||
|
--cache-dir <cache-directory> Set the cache directory.
|
||||||
|
--cache-ttl <cache-ttl> Typhoeus cache TTL.
|
||||||
|
--request-timeout <request-timeout> Request Timeout.
|
||||||
|
--connect-timeout <connect-timeout> Connect Timeout.
|
||||||
|
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
|
||||||
|
--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.
|
||||||
|
|
||||||
--config-file | -c <config file> Use the specified config file, see the example.conf.json
|
# WPSCAN EXAMPLES
|
||||||
|
|
||||||
--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 brute forcer.
|
|
||||||
|
|
||||||
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
|
|
||||||
|
|
||||||
--username | -U <username> Only brute force the supplied username.
|
|
||||||
|
|
||||||
--usernames <path-to-file> Only brute force the usernames from the file.
|
|
||||||
|
|
||||||
--cache-ttl <cache-ttl> Typhoeus cache TTL.
|
|
||||||
|
|
||||||
--request-timeout <request-timeout> Request Timeout.
|
|
||||||
|
|
||||||
--connect-timeout <connect-timeout> Connect Timeout.
|
|
||||||
|
|
||||||
--max-threads <max-threads> Maximum Threads.
|
|
||||||
|
|
||||||
--help | -h This help screen.
|
|
||||||
|
|
||||||
--verbose | -v Verbose output.
|
|
||||||
|
|
||||||
--batch Never ask for user input, use the default behavior.
|
|
||||||
|
|
||||||
--no-color Do not use colors in the output.
|
|
||||||
|
|
||||||
--log Save STDOUT to log.txt
|
|
||||||
|
|
||||||
#### WPSCAN EXAMPLES
|
|
||||||
|
|
||||||
Do 'non-intrusive' checks...
|
Do 'non-intrusive' checks...
|
||||||
|
|
||||||
@@ -310,41 +297,22 @@ Debug output...
|
|||||||
|
|
||||||
```ruby wpscan.rb --url www.example.com --debug-output 2>debug.log```
|
```ruby wpscan.rb --url www.example.com --debug-output 2>debug.log```
|
||||||
|
|
||||||
#### WPSTOOLS ARGUMENTS
|
# PROJECT HOME
|
||||||
|
|
||||||
-v, --verbose Verbose output
|
|
||||||
--check-vuln-ref-urls, --cvru Check all the vulnerabilities reference urls for 404
|
|
||||||
--check-local-vulnerable-files, --clvf LOCAL_DIRECTORY Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells
|
|
||||||
-s, --stats Show WpScan Database statistics.
|
|
||||||
--spellcheck, --sc Check all files for common spelling mistakes.
|
|
||||||
|
|
||||||
|
|
||||||
#### WPSTOOLS EXAMPLES
|
|
||||||
|
|
||||||
Locally scan a wordpress installation for vulnerable files or shells:
|
|
||||||
|
|
||||||
```ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/```
|
|
||||||
|
|
||||||
#### PROJECT HOME
|
|
||||||
|
|
||||||
[http://www.wpscan.org](http://www.wpscan.org)
|
[http://www.wpscan.org](http://www.wpscan.org)
|
||||||
|
|
||||||
#### VULNERABILITY DATABASE
|
# VULNERABILITY DATABASE
|
||||||
|
|
||||||
[https://www.wpvulndb.com](https://www.wpvulndb.com)
|
[https://wpvulndb.com](https://wpvulndb.com)
|
||||||
|
|
||||||
#### GIT REPOSITORY
|
# GIT REPOSITORY
|
||||||
|
|
||||||
[https://github.com/wpscanteam/wpscan](https://github.com/wpscanteam/wpscan)
|
[https://github.com/wpscanteam/wpscan](https://github.com/wpscanteam/wpscan)
|
||||||
|
|
||||||
#### ISSUES
|
# ISSUES
|
||||||
|
|
||||||
[https://github.com/wpscanteam/wpscan/issues](https://github.com/wpscanteam/wpscan/issues)
|
[https://github.com/wpscanteam/wpscan/issues](https://github.com/wpscanteam/wpscan/issues)
|
||||||
|
|
||||||
#### DEVELOPER DOCUMENTATION
|
# DEVELOPER DOCUMENTATION
|
||||||
|
|
||||||
[http://rdoc.info/github/wpscanteam/wpscan/frames](http://rdoc.info/github/wpscanteam/wpscan/frames)
|
[http://rdoc.info/github/wpscanteam/wpscan/frames](http://rdoc.info/github/wpscanteam/wpscan/frames)
|
||||||
|
|
||||||
#### SPECIAL THANKS
|
|
||||||
|
|
||||||
[RandomStorm](https://www.randomstorm.com)
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ end
|
|||||||
html = open(html_path).read
|
html = open(html_path).read
|
||||||
examples = html.match(/(\d+) examples/)[0].to_i rescue 0
|
examples = html.match(/(\d+) examples/)[0].to_i rescue 0
|
||||||
errors = html.match(/(\d+) errors/)[0].to_i rescue 0
|
errors = html.match(/(\d+) errors/)[0].to_i rescue 0
|
||||||
if errors == 0 then
|
if errors == 0
|
||||||
errors = html.match(/(\d+) failure/)[0].to_i rescue 0
|
errors = html.match(/(\d+) failure/)[0].to_i rescue 0
|
||||||
end
|
end
|
||||||
pending = html.match(/(\d+) pending/)[0].to_i rescue 0
|
pending = html.match(/(\d+) pending/)[0].to_i rescue 0
|
||||||
|
|||||||
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.expand_path(File.join(__dir__, '..', '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: #{wordpress_json.map {|k,v| v['vulnerabilities'].count}.inject(:+)}"
|
||||||
|
puts "* Total plugin vulnerabilities: #{plugins_json.map {|k,v| v['vulnerabilities'].count}.inject(:+)}"
|
||||||
|
puts "* Total theme vulnerabilities: #{themes_json.map {|k,v| v['vulnerabilities'].count}.inject(:+)}"
|
||||||
@@ -2,17 +2,17 @@
|
|||||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0",
|
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0",
|
||||||
|
|
||||||
/* Uncomment the "proxy" line to use the proxy
|
/* Uncomment the "proxy" line to use the proxy
|
||||||
SOCKS proxies (4, 4A, 5) are supported, ie : "proxy": "socks5://127.0.0.1:9000"
|
SOCKS proxies (4, 4A, 5) are supported, ie : "proxy": "socks5://127.0.0.1:9000"
|
||||||
If you do not specify the protocol, http will be used
|
If you do not specify the protocol, http will be used
|
||||||
*/
|
*/
|
||||||
//"proxy": "127.0.0.1:3128",
|
//"proxy": "127.0.0.1:3128",
|
||||||
//"proxy_auth": "username:password",
|
//"proxy_auth": "username:password",
|
||||||
|
|
||||||
"cache_ttl": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
|
"cache_ttl": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
|
||||||
|
|
||||||
"request_timeout": 2000, // 2s
|
"request_timeout": 60, // 1min
|
||||||
|
|
||||||
"connect_timeout": 1000, // 1s
|
"connect_timeout": 10, // 10s
|
||||||
|
|
||||||
"max_threads": 20
|
"max_threads": 20
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,18 @@ class Browser
|
|||||||
:proxy_auth,
|
:proxy_auth,
|
||||||
:request_timeout,
|
:request_timeout,
|
||||||
:connect_timeout,
|
:connect_timeout,
|
||||||
:cookie
|
:cookie,
|
||||||
|
:throttle,
|
||||||
|
:disable_accept_header,
|
||||||
|
:disable_referer,
|
||||||
|
:disable_tls_checks
|
||||||
]
|
]
|
||||||
|
|
||||||
@@instance = nil
|
@@instance = nil
|
||||||
|
|
||||||
attr_reader :hydra, :cache_dir
|
attr_reader :hydra, :cache_dir
|
||||||
|
|
||||||
attr_accessor :referer, :cookie
|
attr_accessor :referer, :cookie, :vhost
|
||||||
|
|
||||||
# @param [ Hash ] options
|
# @param [ Hash ] options
|
||||||
#
|
#
|
||||||
@@ -66,18 +70,24 @@ class Browser
|
|||||||
@@instance = nil
|
@@instance = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Override for setting the User-Agent
|
||||||
|
# @param [ String ] user_agent
|
||||||
|
def user_agent=(user_agent)
|
||||||
|
Typhoeus::Config.user_agent = user_agent
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# sets browser default values
|
# sets browser default values
|
||||||
#
|
#
|
||||||
def browser_defaults
|
def browser_defaults
|
||||||
@max_threads = 20
|
Typhoeus::Config.user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
|
||||||
# 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
|
@max_threads = 20
|
||||||
@cache_ttl = 600
|
# 10 minutes, at this time the cache is cleaned before each scan.
|
||||||
# 2s
|
# If this value is set to 0, the cache will be disabled
|
||||||
@request_timeout = 2000
|
@cache_ttl = 600
|
||||||
# 1s
|
@request_timeout = 60 # 60s
|
||||||
@connect_timeout = 1000
|
@connect_timeout = 10 # 10s
|
||||||
@user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
|
@throttle = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -88,7 +98,6 @@ class Browser
|
|||||||
#
|
#
|
||||||
# @return [ void ]
|
# @return [ void ]
|
||||||
def load_config(config_file = nil)
|
def load_config(config_file = nil)
|
||||||
|
|
||||||
if File.symlink?(config_file)
|
if File.symlink?(config_file)
|
||||||
raise '[ERROR] Config file is a symlink.'
|
raise '[ERROR] Config file is a symlink.'
|
||||||
else
|
else
|
||||||
@@ -101,7 +110,6 @@ class Browser
|
|||||||
self.send(:"#{option_name}=", data[option_name])
|
self.send(:"#{option_name}=", data[option_name])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ String ] url
|
# @param [ String ] url
|
||||||
@@ -116,18 +124,9 @@ class Browser
|
|||||||
#
|
#
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def merge_request_params(params = {})
|
def merge_request_params(params = {})
|
||||||
params = Browser.append_params_header_field(
|
|
||||||
params,
|
|
||||||
'User-Agent',
|
|
||||||
@user_agent
|
|
||||||
)
|
|
||||||
|
|
||||||
if @proxy
|
if @proxy
|
||||||
params = params.merge(proxy: @proxy)
|
params.merge!(proxy: @proxy)
|
||||||
|
params.merge!(proxyauth: @proxy_auth) if @proxy_auth
|
||||||
if @proxy_auth
|
|
||||||
params = params.merge(proxyauth: @proxy_auth)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if @basic_auth
|
if @basic_auth
|
||||||
@@ -138,23 +137,37 @@ class Browser
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if vhost
|
||||||
|
params = Browser.append_params_header_field(
|
||||||
|
params,
|
||||||
|
'Host',
|
||||||
|
vhost
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
params.merge!(referer: referer)
|
params.merge!(referer: referer)
|
||||||
params.merge!(timeout: @request_timeout) if @request_timeout
|
params.merge!(timeout: @request_timeout) if @request_timeout && !params.key?(:timeout)
|
||||||
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout
|
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout && !params.key?(:connecttimeout)
|
||||||
|
|
||||||
# Used to enable the cache system if :cache_ttl > 0
|
# Used to enable the cache system if :cache_ttl > 0
|
||||||
params.merge!(cache_ttl: @cache_ttl) unless params.has_key?(:cache_ttl)
|
params.merge!(cache_ttl: @cache_ttl) unless params.key?(:cache_ttl)
|
||||||
|
|
||||||
# Prevent infinite self redirection
|
# Prevent infinite self redirection
|
||||||
params.merge!(maxredirs: 3) unless params.has_key?(:maxredirs)
|
params.merge!(maxredirs: 3) unless params.key?(:maxredirs)
|
||||||
|
|
||||||
# Disable SSL-Certificate checks
|
# Disable SSL-Certificate checks
|
||||||
params.merge!(ssl_verifypeer: false)
|
if @disable_tls_checks
|
||||||
params.merge!(ssl_verifyhost: 0)
|
# Cert validity check
|
||||||
|
params.merge!(ssl_verifypeer: 0) unless params.key?(:ssl_verifypeer)
|
||||||
|
# Cert hostname check
|
||||||
|
params.merge!(ssl_verifyhost: 0) unless params.key?(:ssl_verifyhost)
|
||||||
|
end
|
||||||
|
|
||||||
params.merge!(cookiejar: @cache_dir + '/cookie-jar')
|
params.merge!(cookiejar: @cache_dir + '/cookie-jar')
|
||||||
params.merge!(cookiefile: @cache_dir + '/cookie-jar')
|
params.merge!(cookiefile: @cache_dir + '/cookie-jar')
|
||||||
params.merge!(cookie: @cookie) if @cookie
|
params.merge!(cookie: @cookie) if @cookie
|
||||||
|
params = Browser.remove_params_header_field(params, 'Accept') if @disable_accept_header
|
||||||
|
params = Browser.remove_params_header_field(params, 'Referer') if @disable_referer
|
||||||
|
|
||||||
params
|
params
|
||||||
end
|
end
|
||||||
@@ -175,4 +188,17 @@ class Browser
|
|||||||
params
|
params
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param [ Hash ] params
|
||||||
|
# @param [ String ] field
|
||||||
|
# @param [ Mixed ] field_value
|
||||||
|
#
|
||||||
|
# @return [ Array ]
|
||||||
|
def self.remove_params_header_field(params = {}, field)
|
||||||
|
if !params.has_key?(:headers)
|
||||||
|
params = params.merge(:headers => { field => nil })
|
||||||
|
elsif !params[:headers].has_key?(field)
|
||||||
|
params[:headers][field] = nil
|
||||||
|
end
|
||||||
|
params
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
class Browser
|
class Browser
|
||||||
module Options
|
module Options
|
||||||
|
|
||||||
attr_accessor :cache_ttl, :request_timeout, :connect_timeout
|
attr_accessor :request_timeout, :connect_timeout, :user_agent, :disable_accept_header, :disable_referer, :disable_tls_checks
|
||||||
attr_reader :basic_auth, :proxy, :proxy_auth
|
attr_reader :basic_auth, :cache_ttl, :proxy, :proxy_auth, :throttle
|
||||||
attr_writer :user_agent
|
|
||||||
|
|
||||||
# Sets the Basic Authentification credentials
|
# Sets the Basic Authentification credentials
|
||||||
# Accepted format:
|
# Accepted format:
|
||||||
@@ -25,6 +24,10 @@ class Browser
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cache_ttl=(ttl)
|
||||||
|
@cache_ttl = ttl.to_i
|
||||||
|
end
|
||||||
|
|
||||||
# @return [ Integer ]
|
# @return [ Integer ]
|
||||||
def max_threads
|
def max_threads
|
||||||
@max_threads || 1
|
@max_threads || 1
|
||||||
@@ -93,6 +96,11 @@ class Browser
|
|||||||
@connect_timeout = timeout.to_i
|
@connect_timeout = timeout.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param [ String, Integer ] throttle
|
||||||
|
def throttle=(throttle)
|
||||||
|
@throttle = throttle.to_i.abs / 1000.0
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def invalid_proxy_auth_format
|
def invalid_proxy_auth_format
|
||||||
@@ -110,6 +118,5 @@ class Browser
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ class CacheFileStore
|
|||||||
@storage_path = File.expand_path(File.join(storage_path, storage_dir))
|
@storage_path = File.expand_path(File.join(storage_path, storage_dir))
|
||||||
@serializer = serializer
|
@serializer = serializer
|
||||||
|
|
||||||
# File.directory? for ruby <= 1.9 otherwise,
|
unless Dir.exist?(@storage_path)
|
||||||
# it makes more sense to do Dir.exist? :/
|
|
||||||
unless File.directory?(@storage_path)
|
|
||||||
FileUtils.mkdir_p(@storage_path)
|
FileUtils.mkdir_p(@storage_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -51,7 +49,7 @@ class CacheFileStore
|
|||||||
end
|
end
|
||||||
|
|
||||||
def write_entry(key, data_to_store, cache_ttl)
|
def write_entry(key, data_to_store, cache_ttl)
|
||||||
if cache_ttl > 0
|
if cache_ttl && cache_ttl > 0
|
||||||
File.open(get_entry_file_path(key), 'w') do |f|
|
File.open(get_entry_file_path(key), 'w') do |f|
|
||||||
begin
|
begin
|
||||||
f.write(@serializer.dump(data_to_store))
|
f.write(@serializer.dump(data_to_store))
|
||||||
|
|||||||
149
lib/common/collections/wp_items.rb
Executable file → Normal file
149
lib/common/collections/wp_items.rb
Executable file → Normal file
@@ -1,74 +1,75 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'common/collections/wp_items/detectable'
|
require 'common/collections/wp_items/detectable'
|
||||||
require 'common/collections/wp_items/output'
|
require 'common/collections/wp_items/output'
|
||||||
|
|
||||||
class WpItems < Array
|
class WpItems < Array
|
||||||
extend WpItems::Detectable
|
extend WpItems::Detectable
|
||||||
include WpItems::Output
|
include WpItems::Output
|
||||||
|
|
||||||
attr_accessor :wp_target
|
attr_accessor :wp_target
|
||||||
|
|
||||||
# @param [ WpTarget ] wp_target
|
# @param [ WpTarget ] wp_target
|
||||||
def initialize(wp_target = nil)
|
def initialize(wp_target = nil)
|
||||||
self.wp_target = wp_target
|
self.wp_target = wp_target
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [String] argv
|
# @param [String] args
|
||||||
#
|
#
|
||||||
# @return [ void ]
|
# @return [ void ]
|
||||||
def add(*args)
|
def add(*args)
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
until args[index].nil?
|
until args[index].nil?
|
||||||
arg = args[index]
|
arg = args[index]
|
||||||
|
|
||||||
if arg.is_a?(String)
|
if arg.is_a?(String)
|
||||||
if (next_arg = args[index + 1]).is_a?(Hash)
|
if (next_arg = args[index + 1]).is_a?(Hash)
|
||||||
item = create_item(arg, next_arg)
|
item = create_item(arg, next_arg)
|
||||||
index += 1
|
index += 1
|
||||||
else
|
else
|
||||||
item = create_item(arg)
|
item = create_item(arg)
|
||||||
end
|
end
|
||||||
elsif arg.is_a?(Item)
|
elsif arg.is_a?(Item)
|
||||||
item = arg
|
item = arg
|
||||||
else
|
else
|
||||||
raise 'Invalid arguments'
|
raise 'Invalid arguments'
|
||||||
end
|
end
|
||||||
|
|
||||||
self << item
|
self << item
|
||||||
index += 1
|
index += 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ String ] name
|
# @param [ String ] name
|
||||||
# @param [ Hash ] attrs
|
# @param [ Hash ] attrs
|
||||||
#
|
#
|
||||||
# @return [ WpItem ]
|
# @return [ WpItem ]
|
||||||
def create_item(name, attrs = {})
|
def create_item(name, attrs = {})
|
||||||
raise 'wp_target must be set' unless wp_target
|
raise 'wp_target must be set' unless wp_target
|
||||||
|
|
||||||
item_class.new(
|
item_class.new(
|
||||||
wp_target.uri,
|
wp_target.uri,
|
||||||
attrs.merge(
|
attrs.merge(
|
||||||
name: name,
|
name: name,
|
||||||
wp_content_dir: wp_target.wp_content_dir,
|
wp_content_dir: wp_target.wp_content_dir,
|
||||||
wp_plugins_dir: wp_target.wp_plugins_dir
|
wp_plugins_dir: wp_target.wp_plugins_dir
|
||||||
) { |key, oldval, newval| oldval }
|
) { |key, oldval, newval| oldval }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ WpItems ] other
|
# @param [ WpItems ] other
|
||||||
#
|
#
|
||||||
# @return [ self ]
|
# @return [ self ]
|
||||||
def +(other)
|
def +(other)
|
||||||
other.each { |item| self << item }
|
other.each { |item| self << item }
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
# @return [ Class ]
|
|
||||||
def item_class
|
# @return [ Class ]
|
||||||
Object.const_get(self.class.to_s.gsub(/.$/, ''))
|
def item_class
|
||||||
end
|
Object.const_get(self.class.to_s.gsub(/.$/, ''))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
478
lib/common/collections/wp_items/detectable.rb
Executable file → Normal file
478
lib/common/collections/wp_items/detectable.rb
Executable file → Normal file
@@ -1,238 +1,240 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpItems < Array
|
class WpItems < Array
|
||||||
module Detectable
|
module Detectable
|
||||||
|
|
||||||
attr_reader :vulns_file, :item_xpath
|
attr_reader :vulns_file, :item_xpath
|
||||||
|
|
||||||
# @param [ WpTarget ] wp_target
|
# @param [ WpTarget ] wp_target
|
||||||
# @param [ Hash ] options
|
# @param [ Hash ] options
|
||||||
# @option options [ Boolean ] :show_progression Whether or not output the progress bar
|
# @option options [ Boolean ] :show_progression Whether or not output the progress bar
|
||||||
# @option options [ Boolean ] :only_vulnerable Only check for vulnerable items
|
# @option options [ Boolean ] :only_vulnerable Only check for vulnerable items
|
||||||
# @option options [ String ] :exclude_content
|
# @option options [ String ] :exclude_content
|
||||||
#
|
#
|
||||||
# @return [ WpItems ]
|
# @return [ WpItems ]
|
||||||
def aggressive_detection(wp_target, options = {})
|
def aggressive_detection(wp_target, options = {})
|
||||||
browser = Browser.instance
|
browser = Browser.instance
|
||||||
hydra = browser.hydra
|
hydra = browser.hydra
|
||||||
targets = targets_items(wp_target, options)
|
targets = targets_items(wp_target, options)
|
||||||
progress_bar = progress_bar(targets.size, options)
|
progress_bar = progress_bar(targets.size, options)
|
||||||
queue_count = 0
|
queue_count = 0
|
||||||
exist_options = {
|
exist_options = {
|
||||||
error_404_hash: wp_target.error_404_hash,
|
error_404_hash: wp_target.error_404_hash,
|
||||||
homepage_hash: wp_target.homepage_hash,
|
homepage_hash: wp_target.homepage_hash,
|
||||||
exclude_content: options[:exclude_content] ? %r{#{options[:exclude_content]}} : nil
|
exclude_content: options[:exclude_content] ? %r{#{options[:exclude_content]}} : nil
|
||||||
}
|
}
|
||||||
results = passive_detection(wp_target, options)
|
results = passive_detection(wp_target, options)
|
||||||
|
|
||||||
targets.each do |target_item|
|
targets.each do |target_item|
|
||||||
request = browser.forge_request(target_item.url, request_params)
|
request = browser.forge_request(target_item.url, request_params)
|
||||||
|
|
||||||
request.on_complete do |response|
|
request.on_complete do |response|
|
||||||
progress_bar.progress += 1 if options[:show_progression]
|
progress_bar.progress += 1 if options[:show_progression]
|
||||||
|
|
||||||
if target_item.exists?(exist_options, response)
|
if target_item.exists?(exist_options, response)
|
||||||
if !results.include?(target_item)
|
results << target_item unless results.include?(target_item)
|
||||||
if !options[:only_vulnerable] || options[:only_vulnerable] && target_item.vulnerable?
|
end
|
||||||
results << target_item
|
end
|
||||||
end
|
|
||||||
end
|
hydra.queue(request)
|
||||||
end
|
queue_count += 1
|
||||||
end
|
|
||||||
|
if queue_count >= browser.max_threads
|
||||||
hydra.queue(request)
|
hydra.run
|
||||||
queue_count += 1
|
queue_count = 0
|
||||||
|
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
|
||||||
if queue_count >= browser.max_threads
|
end
|
||||||
hydra.run
|
end
|
||||||
queue_count = 0
|
|
||||||
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
|
# run the remaining requests
|
||||||
end
|
hydra.run
|
||||||
end
|
|
||||||
|
results.select!(&:vulnerable?) if options[:type] == :vulnerable
|
||||||
# run the remaining requests
|
results.sort!
|
||||||
hydra.run
|
|
||||||
|
results # can't just return results.sort as it would return an array, and we want a WpItems
|
||||||
results.select!(&:vulnerable?) if options[:only_vulnerable]
|
end
|
||||||
results.sort!
|
|
||||||
|
# @param [ Integer ] targets_size
|
||||||
results # can't just return results.sort as it would return an array, and we want a WpItems
|
# @param [ Hash ] options
|
||||||
end
|
#
|
||||||
|
# @return [ ProgressBar ]
|
||||||
# @param [ Integer ] targets_size
|
# :nocov:
|
||||||
# @param [ Hash ] options
|
def progress_bar(targets_size, options)
|
||||||
#
|
if options[:show_progression]
|
||||||
# @return [ ProgressBar ]
|
ProgressBar.create(
|
||||||
# :nocov:
|
format: '%t %a <%B> (%c / %C) %P%% %e',
|
||||||
def progress_bar(targets_size, options)
|
title: ' ', # Used to craete a left margin
|
||||||
if options[:show_progression]
|
total: targets_size
|
||||||
ProgressBar.create(
|
)
|
||||||
format: '%t %a <%B> (%c / %C) %P%% %e',
|
end
|
||||||
title: ' ', # Used to craete a left margin
|
end
|
||||||
total: targets_size
|
# :nocov:
|
||||||
)
|
|
||||||
end
|
# @param [ WpTarget ] wp_target
|
||||||
end
|
# @param [ Hash ] options
|
||||||
# :nocov:
|
#
|
||||||
|
# @return [ WpItems ]
|
||||||
# @param [ WpTarget ] wp_target
|
def passive_detection(wp_target, options = {})
|
||||||
# @param [ Hash ] options
|
results = new(wp_target)
|
||||||
#
|
# improves speed
|
||||||
# @return [ WpItems ]
|
body = remove_base64_images_from_html(Browser.get(wp_target.url).body)
|
||||||
def passive_detection(wp_target, options = {})
|
page = Nokogiri::HTML(body)
|
||||||
results = new(wp_target)
|
names = []
|
||||||
# improves speed
|
|
||||||
body = remove_base64_images_from_html(Browser.get(wp_target.url).body)
|
page.css('link,script,style').each do |tag|
|
||||||
page = Nokogiri::HTML(body)
|
%w(href src).each do |attribute|
|
||||||
names = []
|
attr_value = tag.attribute(attribute).to_s
|
||||||
|
next unless attr_value
|
||||||
page.css('link,script,style').each do |tag|
|
|
||||||
%w(href src).each do |attribute|
|
names << Regexp.last_match[1] if attr_value.match(attribute_pattern(wp_target))
|
||||||
attr_value = tag.attribute(attribute).to_s
|
end
|
||||||
next unless attr_value
|
|
||||||
|
next unless tag.name == 'script' || tag.name == 'style'
|
||||||
names << Regexp.last_match[1] if attr_value.match(attribute_pattern(wp_target))
|
|
||||||
end
|
code = tag.text.to_s
|
||||||
|
next if code.empty?
|
||||||
next unless tag.name == 'script' || tag.name == 'style'
|
|
||||||
|
if ! code.valid_encoding?
|
||||||
code = tag.text.to_s
|
code = code.encode('UTF-16be', :invalid => :replace, :replace => '?').encode('UTF-8')
|
||||||
next if code.empty?
|
end
|
||||||
|
|
||||||
code.scan(code_pattern(wp_target)).flatten.uniq.each do |item_name|
|
code.scan(code_pattern(wp_target)).flatten.uniq.each do |item_name|
|
||||||
names << item_name
|
names << item_name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
names.uniq.each { |name| results.add(name) }
|
names.uniq.each { |name| results.add(name) }
|
||||||
|
|
||||||
results.sort!
|
results.sort!
|
||||||
results
|
results
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
# @param [ WpTarget ] wp_target
|
# @param [ WpTarget ] wp_target
|
||||||
#
|
#
|
||||||
# @return [ Regex ]
|
# @return [ Regex ]
|
||||||
def item_pattern(wp_target)
|
def item_pattern(wp_target)
|
||||||
type = to_s.gsub(/Wp/, '').downcase
|
type = to_s.gsub(/Wp/, '').downcase
|
||||||
wp_content_dir = wp_target.wp_content_dir
|
wp_content_dir = wp_target.wp_content_dir
|
||||||
wp_content_url = wp_target.uri.merge(wp_content_dir).to_s
|
wp_content_url = wp_target.uri.merge(wp_content_dir).to_s
|
||||||
|
|
||||||
url = /#{wp_content_url.gsub(%r{\A(?:http|https)}, 'https?').gsub('/', '\\\\\?\/')}/i
|
url = wp_content_url.gsub(%r{\A(?:http|https)://}, '(?:https?:)?//').gsub('/', '\\\\\?\/')
|
||||||
content_dir = %r{(?:#{url}|\\?\/\\?\/?#{wp_content_dir})}i
|
content_dir = %r{(?:#{url}|\\?\/\\?\/?#{wp_content_dir})}i
|
||||||
|
|
||||||
%r{#{content_dir}\\?/#{type}\\?/}
|
%r{#{content_dir}\\?/#{type}\\?/}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ WpTarget ] wp_target
|
# @param [ WpTarget ] wp_target
|
||||||
#
|
#
|
||||||
# @return [ Regex ]
|
# @return [ Regex ]
|
||||||
def attribute_pattern(wp_target)
|
def attribute_pattern(wp_target)
|
||||||
/\A#{item_pattern(wp_target)}([^\/]+)/i
|
/\A#{item_pattern(wp_target)}([^\/]+)/i
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ WpTarget ] wp_target
|
# @param [ WpTarget ] wp_target
|
||||||
#
|
#
|
||||||
# @return [ Regex ]
|
# @return [ Regex ]
|
||||||
def code_pattern(wp_target)
|
def code_pattern(wp_target)
|
||||||
/["'\(]#{item_pattern(wp_target)}([^\\\/\)"']+)/i
|
/["'\(]#{item_pattern(wp_target)}([^\\\/\)"']+)/i
|
||||||
end
|
end
|
||||||
|
|
||||||
# The default request parameters
|
# The default request parameters
|
||||||
#
|
#
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def request_params; { cache_ttl: 0, followlocation: true } end
|
def request_params; { cache_ttl: 0, followlocation: true } end
|
||||||
|
|
||||||
# @param [ WpTarget ] wp_target
|
# @param [ WpTarget ] wp_target
|
||||||
# @param [ options ] options
|
# @param [ options ] options
|
||||||
# @option options [ Boolean ] :only_vulnerable
|
# @option options [ Boolean ] :only_vulnerable
|
||||||
# @option options [ String ] :file The path to the file containing the targets
|
# @option options [ String ] :file The path to the file containing the targets
|
||||||
#
|
#
|
||||||
# @return [ Array<WpItem> ]
|
# @return [ Array<WpItem> ]
|
||||||
def targets_items(wp_target, options = {})
|
def targets_items(wp_target, options = {})
|
||||||
item_class = self.item_class
|
item_class = self.item_class
|
||||||
vulns_file = self.vulns_file
|
vulns_file = self.vulns_file
|
||||||
|
|
||||||
targets = vulnerable_targets_items(wp_target, item_class, vulns_file)
|
targets = target_items_from_type(wp_target, item_class, vulns_file, options[:type])
|
||||||
|
|
||||||
unless options[:only_vulnerable]
|
targets.uniq! { |t| t.name }
|
||||||
unless options[:file]
|
targets.sort_by { rand }
|
||||||
raise 'A file must be supplied'
|
end
|
||||||
end
|
|
||||||
|
# @param [ WpTarget ] wp_target
|
||||||
targets += targets_items_from_file(options[:file], wp_target, item_class, vulns_file)
|
# @param [ Class ] item_class
|
||||||
end
|
# @param [ String ] vulns_file
|
||||||
|
#
|
||||||
targets.uniq! { |t| t.name }
|
# @return [ Array<WpItem> ]
|
||||||
targets.sort_by { rand }
|
def target_items_from_type(wp_target, item_class, vulns_file, type)
|
||||||
end
|
targets = []
|
||||||
|
json = json(vulns_file)
|
||||||
# @param [ WpTarget ] wp_target
|
|
||||||
# @param [ Class ] item_class
|
case type
|
||||||
# @param [ String ] vulns_file
|
when :vulnerable
|
||||||
#
|
items = json.select { |item| !json[item]['vulnerabilities'].empty? }.keys
|
||||||
# @return [ Array<WpItem> ]
|
when :popular
|
||||||
def vulnerable_targets_items(wp_target, item_class, vulns_file)
|
items = json.select { |item| json[item]['popular'] == true }.keys
|
||||||
targets = []
|
when :all
|
||||||
json = json(vulns_file)
|
items = json.keys
|
||||||
|
else
|
||||||
[*json].each do |item|
|
raise "Unknown type #{type}"
|
||||||
targets << create_item(
|
end
|
||||||
item_class,
|
|
||||||
item.keys.inject,
|
items.each do |item|
|
||||||
wp_target,
|
targets << create_item(
|
||||||
vulns_file
|
item_class,
|
||||||
)
|
item,
|
||||||
end
|
wp_target,
|
||||||
|
vulns_file
|
||||||
targets
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Class ] klass
|
targets
|
||||||
# @param [ String ] name
|
end
|
||||||
# @param [ WpTarget ] wp_target
|
|
||||||
# @option [ String ] vulns_file
|
# @param [ Class ] klass
|
||||||
#
|
# @param [ String ] name
|
||||||
# @return [ WpItem ]
|
# @param [ WpTarget ] wp_target
|
||||||
def create_item(klass, name, wp_target, vulns_file = nil)
|
# @option [ String ] vulns_file
|
||||||
klass.new(
|
#
|
||||||
wp_target.uri,
|
# @return [ WpItem ]
|
||||||
name: name,
|
def create_item(klass, name, wp_target, vulns_file = nil)
|
||||||
vulns_file: vulns_file,
|
klass.new(
|
||||||
wp_content_dir: wp_target.wp_content_dir,
|
wp_target.uri,
|
||||||
wp_plugins_dir: wp_target.wp_plugins_dir
|
name: name,
|
||||||
)
|
vulns_file: vulns_file,
|
||||||
end
|
wp_content_dir: wp_target.wp_content_dir,
|
||||||
|
wp_plugins_dir: wp_target.wp_plugins_dir
|
||||||
# @param [ String ] file
|
)
|
||||||
# @param [ WpTarget ] wp_target
|
end
|
||||||
# @param [ Class ] item_class
|
|
||||||
# @param [ String ] vulns_file
|
# @param [ String ] file
|
||||||
#
|
# @param [ WpTarget ] wp_target
|
||||||
# @return [ Array<WpItem> ]
|
# @param [ Class ] item_class
|
||||||
def targets_items_from_file(file, wp_target, item_class, vulns_file)
|
# @param [ String ] vulns_file
|
||||||
targets = []
|
#
|
||||||
|
# @return [ Array<WpItem> ]
|
||||||
File.open(file, 'r') do |f|
|
def targets_items_from_file(file, wp_target, item_class, vulns_file)
|
||||||
f.readlines.collect do |item_name|
|
targets = []
|
||||||
targets << create_item(
|
|
||||||
item_class,
|
File.open(file, 'r') do |f|
|
||||||
item_name.strip,
|
f.readlines.collect do |item_name|
|
||||||
wp_target,
|
targets << create_item(
|
||||||
vulns_file
|
item_class,
|
||||||
)
|
item_name.strip,
|
||||||
end
|
wp_target,
|
||||||
end
|
vulns_file
|
||||||
|
)
|
||||||
targets
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Class ]
|
targets
|
||||||
def item_class
|
end
|
||||||
Object.const_get(self.to_s.gsub(/.$/, ''))
|
|
||||||
end
|
# @return [ Class ]
|
||||||
|
def item_class
|
||||||
end
|
Object.const_get(self.to_s.gsub(/.$/, ''))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
16
lib/common/collections/wp_plugins.rb
Executable file → Normal file
16
lib/common/collections/wp_plugins.rb
Executable file → Normal file
@@ -1,8 +1,8 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'common/collections/wp_plugins/detectable'
|
require 'common/collections/wp_plugins/detectable'
|
||||||
|
|
||||||
class WpPlugins < WpItems
|
class WpPlugins < WpItems
|
||||||
extend WpPlugins::Detectable
|
extend WpPlugins::Detectable
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,17 +2,11 @@
|
|||||||
|
|
||||||
class WpPlugins < WpItems
|
class WpPlugins < WpItems
|
||||||
module Detectable
|
module Detectable
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def vulns_file
|
def vulns_file
|
||||||
PLUGINS_VULNS_FILE
|
PLUGINS_FILE
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
|
||||||
# def item_xpath
|
|
||||||
# '//plugin'
|
|
||||||
# end
|
|
||||||
|
|
||||||
# @param [ WpTarget ] wp_target
|
# @param [ WpTarget ] wp_target
|
||||||
# @param [ Hash ] options
|
# @param [ Hash ] options
|
||||||
#
|
#
|
||||||
@@ -68,10 +62,14 @@ class WpPlugins < WpItems
|
|||||||
wp_plugins.add('all-in-one-seo-pack', version: $1)
|
wp_plugins.add('all-in-one-seo-pack', version: $1)
|
||||||
end
|
end
|
||||||
|
|
||||||
if body =~ /<!-- This site is optimized with the Yoast WordPress SEO plugin v([^\s]+) -/i
|
if body =~ /<!-- This site is optimized with the Yoast (?:WordPress )?SEO plugin v([^\s]+) -/i
|
||||||
wp_plugins.add('wordpress-seo', version: $1)
|
wp_plugins.add('wordpress-seo', version: $1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if body =~ /<!-- Google Universal Analytics for WordPress v([^\s]+) -/i
|
||||||
|
wp_plugins.add('google-universal-analytics', version: $1)
|
||||||
|
end
|
||||||
|
|
||||||
wp_plugins
|
wp_plugins
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
16
lib/common/collections/wp_themes.rb
Executable file → Normal file
16
lib/common/collections/wp_themes.rb
Executable file → Normal file
@@ -1,8 +1,8 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'common/collections/wp_themes/detectable'
|
require 'common/collections/wp_themes/detectable'
|
||||||
|
|
||||||
class WpThemes < WpItems
|
class WpThemes < WpItems
|
||||||
extend WpThemes::Detectable
|
extend WpThemes::Detectable
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,13 +5,7 @@ class WpThemes < WpItems
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def vulns_file
|
def vulns_file
|
||||||
THEMES_VULNS_FILE
|
THEMES_FILE
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
|
||||||
# def item_xpath
|
|
||||||
# '//theme'
|
|
||||||
# end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
16
lib/common/collections/wp_timthumbs.rb
Executable file → Normal file
16
lib/common/collections/wp_timthumbs.rb
Executable file → Normal file
@@ -1,8 +1,8 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'common/collections/wp_timthumbs/detectable'
|
require 'common/collections/wp_timthumbs/detectable'
|
||||||
|
|
||||||
class WpTimthumbs < WpItems
|
class WpTimthumbs < WpItems
|
||||||
extend WpTimthumbs::Detectable
|
extend WpTimthumbs::Detectable
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class WpTimthumbs < WpItems
|
|||||||
|
|
||||||
%w{
|
%w{
|
||||||
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
||||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
|
scripts/timthumb.php tools/timthumb.php functions/timthumb.php thumb.php
|
||||||
}.each do |path|
|
}.each do |path|
|
||||||
wp_timthumb.path = "$wp-content$/themes/#{theme_name}/#{path}"
|
wp_timthumb.path = "$wp-content$/themes/#{theme_name}/#{path}"
|
||||||
|
|
||||||
|
|||||||
22
lib/common/collections/wp_users.rb
Executable file → Normal file
22
lib/common/collections/wp_users.rb
Executable file → Normal file
@@ -1,11 +1,11 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'common/collections/wp_users/detectable'
|
require 'common/collections/wp_users/detectable'
|
||||||
require 'common/collections/wp_users/output'
|
require 'common/collections/wp_users/output'
|
||||||
require 'common/collections/wp_users/brute_forcable'
|
require 'common/collections/wp_users/brute_forcable'
|
||||||
|
|
||||||
class WpUsers < WpItems
|
class WpUsers < WpItems
|
||||||
extend WpUsers::Detectable
|
extend WpUsers::Detectable
|
||||||
include WpUsers::Output
|
include WpUsers::Output
|
||||||
include WpUsers::BruteForcable
|
include WpUsers::BruteForcable
|
||||||
end
|
end
|
||||||
|
|||||||
68
lib/common/collections/wp_users/detectable.rb
Executable file → Normal file
68
lib/common/collections/wp_users/detectable.rb
Executable file → Normal file
@@ -1,34 +1,34 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpUsers < WpItems
|
class WpUsers < WpItems
|
||||||
module Detectable
|
module Detectable
|
||||||
|
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def request_params; {} end
|
def request_params; {} end
|
||||||
|
|
||||||
# No passive detection
|
# No passive detection
|
||||||
#
|
#
|
||||||
# @return [ WpUsers ]
|
# @return [ WpUsers ]
|
||||||
def passive_detection(wp_target, options = {})
|
def passive_detection(wp_target, options = {})
|
||||||
new
|
new
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
# @param [ WpTarget ] wp_target
|
# @param [ WpTarget ] wp_target
|
||||||
# @param [ Hash ] options
|
# @param [ Hash ] options
|
||||||
# @option options [ Range ] :range ((1..10))
|
# @option options [ Range ] :range ((1..10))
|
||||||
#
|
#
|
||||||
# @return [ Array<WpUser> ]
|
# @return [ Array<WpUser> ]
|
||||||
def targets_items(wp_target, options = {})
|
def targets_items(wp_target, options = {})
|
||||||
range = options[:range] || (1..10)
|
range = options[:range] || (1..10)
|
||||||
targets = []
|
targets = []
|
||||||
|
|
||||||
range.each do |user_id|
|
range.each do |user_id|
|
||||||
targets << WpUser.new(wp_target.uri, id: user_id)
|
targets << WpUser.new(wp_target.uri, id: user_id)
|
||||||
end
|
end
|
||||||
targets
|
targets
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
LIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
LIB_DIR = File.expand_path(File.join(__dir__, '..'))
|
||||||
ROOT_DIR = File.expand_path(File.join(LIB_DIR, '..')) # expand_path is used to get "wpscan/" instead of "wpscan/lib/../"
|
ROOT_DIR = File.expand_path(File.join(LIB_DIR, '..')) # expand_path is used to get "wpscan/" instead of "wpscan/lib/../"
|
||||||
DATA_DIR = File.join(ROOT_DIR, 'data')
|
DATA_DIR = File.join(ROOT_DIR, 'data')
|
||||||
CONF_DIR = File.join(ROOT_DIR, 'conf')
|
CONF_DIR = File.join(ROOT_DIR, 'conf')
|
||||||
CACHE_DIR = File.join(ROOT_DIR, 'cache')
|
CACHE_DIR = File.join(ROOT_DIR, 'cache')
|
||||||
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan')
|
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan')
|
||||||
WPSTOOLS_LIB_DIR = File.join(LIB_DIR, 'wpstools')
|
|
||||||
UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater')
|
UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater')
|
||||||
COMMON_LIB_DIR = File.join(LIB_DIR, 'common')
|
COMMON_LIB_DIR = File.join(LIB_DIR, 'common')
|
||||||
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models')
|
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models')
|
||||||
@@ -17,24 +16,21 @@ LOG_FILE = File.join(ROOT_DIR, 'log.txt')
|
|||||||
# Plugins directories
|
# Plugins directories
|
||||||
COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins')
|
COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins')
|
||||||
WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM
|
WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM
|
||||||
WPSTOOLS_PLUGINS_DIR = File.join(WPSTOOLS_LIB_DIR, 'plugins')
|
|
||||||
|
|
||||||
# Data files
|
# Data files
|
||||||
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.txt')
|
WORDPRESSES_FILE = File.join(DATA_DIR, 'wordpresses.json')
|
||||||
PLUGINS_FULL_FILE = File.join(DATA_DIR, 'plugins_full.txt')
|
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.json')
|
||||||
PLUGINS_VULNS_FILE = File.join(DATA_DIR, 'plugin_vulns.json')
|
THEMES_FILE = File.join(DATA_DIR, 'themes.json')
|
||||||
THEMES_FILE = File.join(DATA_DIR, 'themes.txt')
|
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml')
|
||||||
THEMES_FULL_FILE = File.join(DATA_DIR, 'themes_full.txt')
|
LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml')
|
||||||
THEMES_VULNS_FILE = File.join(DATA_DIR, 'theme_vulns.json')
|
WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd')
|
||||||
WP_VULNS_FILE = File.join(DATA_DIR, 'wp_vulns.json')
|
LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd')
|
||||||
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml')
|
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
|
||||||
LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml')
|
LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update')
|
||||||
# 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')
|
|
||||||
|
|
||||||
WPSCAN_VERSION = '2.7'
|
MIN_RUBY_VERSION = '2.1.9'
|
||||||
|
|
||||||
|
WPSCAN_VERSION = '2.9.2'
|
||||||
|
|
||||||
$LOAD_PATH.unshift(LIB_DIR)
|
$LOAD_PATH.unshift(LIB_DIR)
|
||||||
$LOAD_PATH.unshift(WPSCAN_LIB_DIR)
|
$LOAD_PATH.unshift(WPSCAN_LIB_DIR)
|
||||||
@@ -42,22 +38,31 @@ $LOAD_PATH.unshift(MODELS_LIB_DIR)
|
|||||||
|
|
||||||
def kali_linux?
|
def kali_linux?
|
||||||
begin
|
begin
|
||||||
File.readlines("/etc/debian_version").grep(/^kali/i).any?
|
File.readlines('/etc/debian_version').grep(/^kali/i).any?
|
||||||
rescue
|
rescue
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Determins if installed on Windows OS
|
||||||
|
def windows?
|
||||||
|
Gem.win_platform?
|
||||||
|
end
|
||||||
|
|
||||||
require 'environment'
|
require 'environment'
|
||||||
|
|
||||||
|
def escape_glob(s)
|
||||||
|
s.gsub(/[\\\{\}\[\]\*\?]/) { |x| '\\' + x }
|
||||||
|
end
|
||||||
|
|
||||||
# TODO : add an exclude pattern ?
|
# TODO : add an exclude pattern ?
|
||||||
def require_files_from_directory(absolute_dir_path, files_pattern = '*.rb')
|
def require_files_from_directory(absolute_dir_path, files_pattern = '*.rb')
|
||||||
files = Dir[File.join(absolute_dir_path, files_pattern)]
|
files = Dir[File.join(escape_glob(absolute_dir_path), files_pattern)]
|
||||||
|
|
||||||
# Files in the root dir are loaded first, then those in the subdirectories
|
# Files in the root dir are loaded first, then those in the subdirectories
|
||||||
files.sort_by { |file| [file.count("/"), file] }.each do |f|
|
files.sort_by { |file| [file.count('/'), file] }.each do |f|
|
||||||
f = File.expand_path(f)
|
f = File.expand_path(f)
|
||||||
#puts "require #{f}" # Used for debug
|
# puts "require #{f}" # Used for debug
|
||||||
require f
|
require f
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -80,6 +85,20 @@ def missing_db_file?
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def last_update
|
||||||
|
date = nil
|
||||||
|
if File.exists?(LAST_UPDATE_FILE)
|
||||||
|
content = File.read(LAST_UPDATE_FILE)
|
||||||
|
date = Time.parse(content) rescue nil
|
||||||
|
end
|
||||||
|
date
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_required?
|
||||||
|
date = last_update
|
||||||
|
(true if date.nil?) or (date < 5.days.ago)
|
||||||
|
end
|
||||||
|
|
||||||
# Define colors
|
# Define colors
|
||||||
def colorize(text, color_code)
|
def colorize(text, color_code)
|
||||||
if $COLORSWITCH
|
if $COLORSWITCH
|
||||||
@@ -110,19 +129,21 @@ def blue(text)
|
|||||||
end
|
end
|
||||||
|
|
||||||
def critical(text)
|
def critical(text)
|
||||||
red(text)
|
$exit_code += 1 if defined?($exit_code) # hack for undefined var via rspec
|
||||||
|
"#{red('[!]')} #{text}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def warning(text)
|
def warning(text)
|
||||||
amber(text)
|
$exit_code += 1 if defined?($exit_code) # hack for undefined var via rspec
|
||||||
|
"#{amber('[!]')} #{text}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def info(text)
|
def info(text)
|
||||||
green(text)
|
"#{green('[+]')} #{text}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def notice(text)
|
def notice(text)
|
||||||
blue(text)
|
"#{blue('[i]')} #{text}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# our 1337 banner
|
# our 1337 banner
|
||||||
@@ -130,7 +151,7 @@ def banner
|
|||||||
puts '_______________________________________________________________'
|
puts '_______________________________________________________________'
|
||||||
puts ' __ _______ _____ '
|
puts ' __ _______ _____ '
|
||||||
puts ' \\ \\ / / __ \\ / ____| '
|
puts ' \\ \\ / / __ \\ / ____| '
|
||||||
puts ' \\ \\ /\\ / /| |__) | (___ ___ __ _ _ __ '
|
puts ' \\ \\ /\\ / /| |__) | (___ ___ __ _ _ __ ®'
|
||||||
puts ' \\ \\/ \\/ / | ___/ \\___ \\ / __|/ _` | \'_ \\ '
|
puts ' \\ \\/ \\/ / | ___/ \\___ \\ / __|/ _` | \'_ \\ '
|
||||||
puts ' \\ /\\ / | | ____) | (__| (_| | | | |'
|
puts ' \\ /\\ / | | ____) | (__| (_| | | | |'
|
||||||
puts ' \\/ \\/ |_| |_____/ \\___|\\__,_|_| |_|'
|
puts ' \\/ \\/ |_| |_____/ \\___|\\__,_|_| |_|'
|
||||||
@@ -209,7 +230,11 @@ end
|
|||||||
#
|
#
|
||||||
# @return [ Integer ] The number of lines in the given file
|
# @return [ Integer ] The number of lines in the given file
|
||||||
def count_file_lines(file)
|
def count_file_lines(file)
|
||||||
`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
|
end
|
||||||
|
|
||||||
# Truncates a string to a specific length and adds ... at the end
|
# Truncates a string to a specific length and adds ... at the end
|
||||||
@@ -243,3 +268,7 @@ end
|
|||||||
def directory_listing_enabled?(url)
|
def directory_listing_enabled?(url)
|
||||||
Browser.get(url.to_s).body[%r{<title>Index of}] ? true : false
|
Browser.get(url.to_s).body[%r{<title>Index of}] ? true : false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def url_encode(str)
|
||||||
|
CGI.escape(str).gsub("+", "%20")
|
||||||
|
end
|
||||||
|
|||||||
@@ -4,9 +4,8 @@
|
|||||||
class DbUpdater
|
class DbUpdater
|
||||||
FILES = %w(
|
FILES = %w(
|
||||||
local_vulnerable_files.xml local_vulnerable_files.xsd
|
local_vulnerable_files.xml local_vulnerable_files.xsd
|
||||||
plugins_full.txt plugins.txt themes_full.txt themes.txt
|
|
||||||
timthumbs.txt user-agents.txt wp_versions.xml wp_versions.xsd
|
timthumbs.txt user-agents.txt wp_versions.xml wp_versions.xsd
|
||||||
plugin_vulns.json theme_vulns.json wp_vulns.json LICENSE
|
wordpresses.json plugins.json themes.json LICENSE
|
||||||
)
|
)
|
||||||
|
|
||||||
attr_reader :repo_directory
|
attr_reader :repo_directory
|
||||||
@@ -22,13 +21,16 @@ class DbUpdater
|
|||||||
def request_params
|
def request_params
|
||||||
{
|
{
|
||||||
ssl_verifyhost: 2,
|
ssl_verifyhost: 2,
|
||||||
ssl_verifypeer: true
|
ssl_verifypeer: true,
|
||||||
|
accept_encoding: 'gzip, deflate',
|
||||||
|
timeout: 300,
|
||||||
|
connecttimeout: 20
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ] The raw file URL associated with the given filename
|
# @return [ String ] The raw file URL associated with the given filename
|
||||||
def remote_file_url(filename)
|
def remote_file_url(filename)
|
||||||
"https://wpvulndb.com/data/#{filename}"
|
"https://data.wpscan.org/#{filename}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ] The checksum of the associated remote filename
|
# @return [ String ] The checksum of the associated remote filename
|
||||||
@@ -36,8 +38,8 @@ class DbUpdater
|
|||||||
url = "#{remote_file_url(filename)}.sha512"
|
url = "#{remote_file_url(filename)}.sha512"
|
||||||
|
|
||||||
res = Browser.get(url, request_params)
|
res = Browser.get(url, request_params)
|
||||||
fail "Unable to get #{url}" unless res.code == 200
|
fail DownloadError, res if res.timed_out? || res.code != 200
|
||||||
res.body
|
res.body.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
def local_file_path(filename)
|
def local_file_path(filename)
|
||||||
@@ -72,7 +74,7 @@ class DbUpdater
|
|||||||
file_url = remote_file_url(filename)
|
file_url = remote_file_url(filename)
|
||||||
|
|
||||||
res = Browser.get(file_url, request_params)
|
res = Browser.get(file_url, request_params)
|
||||||
fail "Error while downloading #{file_url}" unless res.code == 200
|
fail DownloadError, res if res.timed_out? || res.code != 200
|
||||||
File.open(file_path, 'wb') { |f| f.write(res.body) }
|
File.open(file_path, 'wb') { |f| f.write(res.body) }
|
||||||
|
|
||||||
local_file_checksum(filename)
|
local_file_checksum(filename)
|
||||||
@@ -96,9 +98,10 @@ class DbUpdater
|
|||||||
puts ' [i] Downloading new file' if verbose
|
puts ' [i] Downloading new file' if verbose
|
||||||
dl_checksum = download(filename)
|
dl_checksum = download(filename)
|
||||||
puts " [i] Downloaded File Checksum: #{dl_checksum}" if verbose
|
puts " [i] Downloaded File Checksum: #{dl_checksum}" if verbose
|
||||||
|
puts " [i] Database File Checksum : #{db_checksum}" if verbose
|
||||||
|
|
||||||
unless dl_checksum == db_checksum
|
unless dl_checksum == db_checksum
|
||||||
fail "#{filename}: checksums do not match"
|
raise ChecksumError.new(File.read(local_file_path(filename))), "#{filename}: checksums do not match (local: #{dl_checksum} remote: #{db_checksum})"
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
puts ' [i] Restoring Backup due to error' if verbose
|
puts ' [i] Restoring Backup due to error' if verbose
|
||||||
@@ -111,5 +114,8 @@ class DbUpdater
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# write last_update date to file
|
||||||
|
File.write(LAST_UPDATE_FILE, Time.now)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
41
lib/common/errors.rb
Normal file
41
lib/common/errors.rb
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
class ChecksumError < StandardError
|
||||||
|
attr_reader :file
|
||||||
|
|
||||||
|
def initialize(file)
|
||||||
|
@file = file
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,35 +1,5 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
# Since ruby 1.9.2, URI::escape is obsolete
|
|
||||||
# See http://rosettacode.org/wiki/URL_encoding#Ruby and http://www.ruby-forum.com/topic/207489
|
|
||||||
if RUBY_VERSION >= '1.9.2'
|
|
||||||
module URI
|
|
||||||
extend self
|
|
||||||
|
|
||||||
def escape(str)
|
|
||||||
URI::Parser.new.escape(str)
|
|
||||||
end
|
|
||||||
alias :encode :escape
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if RUBY_VERSION < '1.9'
|
|
||||||
class Array
|
|
||||||
# Fix for grep with symbols in ruby <= 1.8.7
|
|
||||||
def _grep_(regexp)
|
|
||||||
matches = []
|
|
||||||
self.each do |value|
|
|
||||||
value = value.to_s
|
|
||||||
matches << value if value.match(regexp)
|
|
||||||
end
|
|
||||||
matches
|
|
||||||
end
|
|
||||||
|
|
||||||
alias_method :grep, :_grep_
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This is used in WpItem::Existable
|
# This is used in WpItem::Existable
|
||||||
module Typhoeus
|
module Typhoeus
|
||||||
class Response
|
class Response
|
||||||
@@ -53,56 +23,28 @@ def puts(o = '')
|
|||||||
temp = o.gsub(/\e\[\d+m/, '') # remove color for logging
|
temp = o.gsub(/\e\[\d+m/, '') # remove color for logging
|
||||||
File.open(LOG_FILE, 'a+') { |f| f.puts(temp) }
|
File.open(LOG_FILE, 'a+') { |f| f.puts(temp) }
|
||||||
end
|
end
|
||||||
|
|
||||||
super(o)
|
super(o)
|
||||||
end
|
end
|
||||||
|
|
||||||
module Terminal
|
|
||||||
class Table
|
|
||||||
def render
|
|
||||||
separator = Separator.new(self)
|
|
||||||
buffer = [separator]
|
|
||||||
unless @title.nil?
|
|
||||||
buffer << Row.new(self, [title_cell_options])
|
|
||||||
buffer << separator
|
|
||||||
end
|
|
||||||
unless @headings.cells.empty?
|
|
||||||
buffer << @headings
|
|
||||||
buffer << separator
|
|
||||||
end
|
|
||||||
buffer += @rows
|
|
||||||
buffer << separator
|
|
||||||
buffer.map { |r| style.margin_left + r.render }.join("\n")
|
|
||||||
end
|
|
||||||
alias :to_s :render
|
|
||||||
|
|
||||||
class Style
|
|
||||||
@@defaults = {
|
|
||||||
:border_x => "-", :border_y => "|", :border_i => "+",
|
|
||||||
:padding_left => 1, :padding_right => 1,
|
|
||||||
:margin_left => '',
|
|
||||||
:width => nil, :alignment => nil
|
|
||||||
}
|
|
||||||
|
|
||||||
attr_accessor :margin_left
|
|
||||||
attr_accessor :border_x
|
|
||||||
attr_accessor :border_y
|
|
||||||
attr_accessor :border_i
|
|
||||||
|
|
||||||
attr_accessor :padding_left
|
|
||||||
attr_accessor :padding_right
|
|
||||||
|
|
||||||
attr_accessor :width
|
|
||||||
attr_accessor :alignment
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Numeric
|
class Numeric
|
||||||
def bytes_to_human
|
def bytes_to_human
|
||||||
units = %w{B KB MB GB TB}
|
units = %w{B KB MB GB TB}
|
||||||
e = (Math.log(self)/Math.log(1024)).floor
|
e = (Math.log(abs)/Math.log(1024)).floor
|
||||||
s = "%.3f" % (to_f / 1024**e)
|
s = '%.3f' % (abs.to_f / 1024**e)
|
||||||
s.sub(/\.?0*$/, ' ' + units[e])
|
s.sub(/\.?0*$/, ' ' + units[e])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# time calculations
|
||||||
|
class Fixnum
|
||||||
|
SECONDS_IN_DAY = 24 * 60 * 60
|
||||||
|
|
||||||
|
def days
|
||||||
|
self * SECONDS_IN_DAY
|
||||||
|
end
|
||||||
|
|
||||||
|
def ago
|
||||||
|
Time.now - self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
123
lib/common/models/vulnerability.rb
Executable file → Normal file
123
lib/common/models/vulnerability.rb
Executable file → Normal file
@@ -1,61 +1,62 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'vulnerability/output'
|
require 'vulnerability/output'
|
||||||
require 'vulnerability/urls'
|
require 'vulnerability/urls'
|
||||||
|
|
||||||
class Vulnerability
|
class Vulnerability
|
||||||
include Vulnerability::Output
|
include Vulnerability::Output
|
||||||
include Vulnerability::Urls
|
include Vulnerability::Urls
|
||||||
|
|
||||||
attr_accessor :title, :references, :type, :fixed_in
|
attr_accessor :title, :references, :type, :fixed_in
|
||||||
|
|
||||||
#
|
#
|
||||||
# @param [ String ] title The title of the vulnerability
|
# @param [ String ] title The title of the vulnerability
|
||||||
# @param [ String ] type The type of the vulnerability
|
# @param [ String ] type The type of the vulnerability
|
||||||
# @param [ Hash ] references References
|
# @param [ Hash ] references References
|
||||||
# @param [ String ] fixed_in Vuln fixed in Version X
|
# @param [ String ] fixed_in Vuln fixed in Version X
|
||||||
#
|
#
|
||||||
# @return [ Vulnerability ]
|
# @return [ Vulnerability ]
|
||||||
def initialize(title, type, references = {}, fixed_in = '')
|
def initialize(title, type, references = {}, fixed_in = '')
|
||||||
@title = title
|
@title = title
|
||||||
@type = type
|
@type = type
|
||||||
@references = references
|
@references = references
|
||||||
@fixed_in = fixed_in
|
@fixed_in = fixed_in
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Vulnerability ] other
|
# @param [ Vulnerability ] other
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
# :nocov:
|
# :nocov:
|
||||||
def ==(other)
|
def ==(other)
|
||||||
title == other.title &&
|
title == other.title &&
|
||||||
type == other.type &&
|
type == other.type &&
|
||||||
references == other.references &&
|
references == other.references &&
|
||||||
fixed_in == other.fixed_in
|
fixed_in == other.fixed_in
|
||||||
end
|
end
|
||||||
# :nocov:
|
# :nocov:
|
||||||
|
|
||||||
# Create the Vulnerability from the json_item
|
# Create the Vulnerability from the json_item
|
||||||
#
|
#
|
||||||
# @param [ Hash ] json_item
|
# @param [ Hash ] json_item
|
||||||
#
|
#
|
||||||
# @return [ Vulnerability ]
|
# @return [ Vulnerability ]
|
||||||
def self.load_from_json_item(json_item)
|
def self.load_from_json_item(json_item)
|
||||||
references = {}
|
references = {}
|
||||||
|
references['id'] = [json_item['id']]
|
||||||
%w(id url cve secunia osvdb metasploit exploitdb).each do |key|
|
|
||||||
if json_item[key]
|
%w(url cve secunia osvdb metasploit exploitdb).each do |key|
|
||||||
json_item[key] = [json_item[key]] if json_item[key].class != Array
|
if json_item['references'][key]
|
||||||
references[key] = json_item[key]
|
json_item['references'][key] = [json_item['references'][key]] if json_item['references'][key].class != Array
|
||||||
end
|
references[key] = json_item['references'][key]
|
||||||
end
|
end
|
||||||
|
end
|
||||||
new(
|
|
||||||
json_item['title'],
|
new(
|
||||||
json_item['type'],
|
json_item['title'],
|
||||||
references,
|
json_item['type'],
|
||||||
json_item['fixed_in'],
|
references,
|
||||||
)
|
json_item['fixed_in']
|
||||||
end
|
)
|
||||||
|
end
|
||||||
end
|
|
||||||
|
end
|
||||||
|
|||||||
@@ -2,21 +2,22 @@
|
|||||||
|
|
||||||
class Vulnerability
|
class Vulnerability
|
||||||
module Output
|
module Output
|
||||||
|
|
||||||
# output the vulnerability
|
# output the vulnerability
|
||||||
def output(verbose = false)
|
def output(verbose = false)
|
||||||
puts
|
puts
|
||||||
puts "#{critical('[!]')} Title: #{title}"
|
puts critical("Title: #{title}")
|
||||||
|
|
||||||
references.each do |key, urls|
|
references.each do |key, urls|
|
||||||
methodname = "url_#{key}"
|
methodname = "url_#{key}"
|
||||||
|
|
||||||
urls.each do |u|
|
urls.each do |u|
|
||||||
|
next unless respond_to?(methodname)
|
||||||
url = send(methodname, u)
|
url = send(methodname, u)
|
||||||
puts " Reference: #{url}" if url
|
puts " Reference: #{url}" if url
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if !fixed_in.nil?
|
|
||||||
puts "#{notice('[i]')} Fixed in: #{fixed_in}"
|
puts notice("Fixed in: #{fixed_in}") if fixed_in
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,31 +6,39 @@ class Vulnerability
|
|||||||
def url_metasploit(module_path)
|
def url_metasploit(module_path)
|
||||||
# remove leading slash
|
# remove leading slash
|
||||||
module_path = module_path.sub(/^\//, '')
|
module_path = module_path.sub(/^\//, '')
|
||||||
"http://www.rapid7.com/db/modules/#{module_path}"
|
"https://www.rapid7.com/db/modules/#{module_path}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_url(url)
|
def url_url(url)
|
||||||
url
|
url
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_cve(cve)
|
def url_cve(id)
|
||||||
"http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-#{cve}"
|
"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-#{id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_osvdb(id)
|
def url_osvdb(id)
|
||||||
"http://osvdb.org/#{id}"
|
"http://osvdb.org/show/osvdb/#{id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_secunia(id)
|
def url_secunia(id)
|
||||||
"https://secunia.com/advisories/#{id}"
|
"https://secunia.com/advisories/#{id}/"
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_exploitdb(id)
|
def url_exploitdb(id)
|
||||||
"http://www.exploit-db.com/exploits/#{id}/"
|
"https://www.exploit-db.com/exploits/#{id}/"
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_id(id)
|
def url_id(id)
|
||||||
"https://wpvulndb.com/vulnerabilities/#{id}"
|
"https://wpvulndb.com/vulnerabilities/#{id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def url_packetstorm(id)
|
||||||
|
"http://packetstormsecurity.com/files/#{id}/"
|
||||||
|
end
|
||||||
|
|
||||||
|
def url_securityfocus(id)
|
||||||
|
"http://www.securityfocus.com/bid/#{id}/"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
224
lib/common/models/wp_item.rb
Executable file → Normal file
224
lib/common/models/wp_item.rb
Executable file → Normal file
@@ -1,103 +1,121 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'wp_item/findable'
|
require 'wp_item/findable'
|
||||||
require 'wp_item/versionable'
|
require 'wp_item/versionable'
|
||||||
require 'wp_item/vulnerable'
|
require 'wp_item/vulnerable'
|
||||||
require 'wp_item/existable'
|
require 'wp_item/existable'
|
||||||
require 'wp_item/infos'
|
require 'wp_item/infos'
|
||||||
require 'wp_item/output'
|
require 'wp_item/output'
|
||||||
|
|
||||||
class WpItem
|
class WpItem
|
||||||
|
|
||||||
extend WpItem::Findable
|
extend WpItem::Findable
|
||||||
include WpItem::Versionable
|
include WpItem::Versionable
|
||||||
include WpItem::Vulnerable
|
include WpItem::Vulnerable
|
||||||
include WpItem::Existable
|
include WpItem::Existable
|
||||||
include WpItem::Infos
|
include WpItem::Infos
|
||||||
include WpItem::Output
|
include WpItem::Output
|
||||||
|
|
||||||
attr_reader :path
|
attr_reader :path
|
||||||
attr_accessor :name, :wp_content_dir, :wp_plugins_dir
|
attr_accessor :name, :wp_content_dir, :wp_plugins_dir
|
||||||
|
|
||||||
# @return [ Array ]
|
# @return [ Array ]
|
||||||
# Make it private ?
|
# Make it private ?
|
||||||
def allowed_options
|
def allowed_options
|
||||||
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :vulns_file]
|
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :db_file]
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ URI ] target_base_uri
|
# @param [ URI ] target_base_uri
|
||||||
# @param [ Hash ] options See allowed_option
|
# @param [ Hash ] options See allowed_option
|
||||||
#
|
#
|
||||||
# @return [ WpItem ]
|
# @return [ WpItem ]
|
||||||
def initialize(target_base_uri, options = {})
|
def initialize(target_base_uri, options = {})
|
||||||
|
options[:wp_content_dir] ||= 'wp-content'
|
||||||
options[:wp_content_dir] ||= 'wp-content'
|
options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins'
|
||||||
options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins'
|
|
||||||
|
set_options(options)
|
||||||
set_options(options)
|
forge_uri(target_base_uri)
|
||||||
forge_uri(target_base_uri)
|
end
|
||||||
end
|
|
||||||
|
def identifier
|
||||||
# @param [ Hash ] options
|
@identifier ||= name
|
||||||
#
|
end
|
||||||
# @return [ void ]
|
|
||||||
def set_options(options)
|
# @return [ Hash ]
|
||||||
allowed_options.each do |allowed_option|
|
def db_data
|
||||||
if options.has_key?(allowed_option)
|
@db_data ||= json(db_file)[identifier] || {}
|
||||||
method = :"#{allowed_option}="
|
end
|
||||||
|
|
||||||
if self.respond_to?(method)
|
def latest_version
|
||||||
self.send(method, options[allowed_option])
|
db_data['latest_version']
|
||||||
else
|
end
|
||||||
raise "#{self.class} does not respond to #{method}"
|
|
||||||
end
|
def last_updated
|
||||||
end
|
db_data['last_ipdated']
|
||||||
end
|
end
|
||||||
end
|
|
||||||
private :set_options
|
def popular?
|
||||||
|
db_data['popular']
|
||||||
# @param [ URI ] target_base_uri
|
end
|
||||||
#
|
|
||||||
# @return [ void ]
|
# @param [ Hash ] options
|
||||||
def forge_uri(target_base_uri)
|
#
|
||||||
@uri = target_base_uri
|
# @return [ void ]
|
||||||
end
|
def set_options(options)
|
||||||
|
allowed_options.each do |allowed_option|
|
||||||
# @return [ URI ] The uri to the WpItem, with the path if present
|
if options.has_key?(allowed_option)
|
||||||
def uri
|
method = :"#{allowed_option}="
|
||||||
path ? @uri.merge(path) : @uri
|
|
||||||
end
|
if self.respond_to?(method)
|
||||||
|
self.send(method, options[allowed_option])
|
||||||
# @return [ String ] The url to the WpItem
|
else
|
||||||
def url; uri.to_s end
|
raise "#{self.class} does not respond to #{method}"
|
||||||
|
end
|
||||||
# Sets the path
|
end
|
||||||
#
|
end
|
||||||
# Variable, such as $wp-plugins$ and $wp-content$ can be used
|
end
|
||||||
# and will be replace by their value
|
private :set_options
|
||||||
#
|
|
||||||
# @param [ String ] path
|
# @param [ URI ] target_base_uri
|
||||||
#
|
#
|
||||||
# @return [ void ]
|
# @return [ void ]
|
||||||
def path=(path)
|
def forge_uri(target_base_uri)
|
||||||
@path = URI.encode(
|
@uri = target_base_uri
|
||||||
path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
|
end
|
||||||
)
|
|
||||||
end
|
# @return [ URI ] The uri to the WpItem, with the path if present
|
||||||
|
def uri
|
||||||
# @param [ WpItem ] other
|
path ? @uri.merge(path) : @uri
|
||||||
def <=>(other)
|
end
|
||||||
name <=> other.name
|
|
||||||
end
|
# @return [ String ] The url to the WpItem
|
||||||
|
def url; uri.to_s end
|
||||||
# @param [ WpItem ] other
|
|
||||||
def ==(other)
|
# Sets the path
|
||||||
name === other.name
|
#
|
||||||
end
|
# Variable, such as $wp-plugins$ and $wp-content$ can be used
|
||||||
|
# and will be replace by their value
|
||||||
# @param [ WpItem ] other
|
#
|
||||||
def ===(other)
|
# @param [ String ] path
|
||||||
self == other && version === other.version
|
#
|
||||||
end
|
# @return [ void ]
|
||||||
|
def path=(path)
|
||||||
end
|
@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
|
||||||
|
|||||||
100
lib/common/models/wp_item/existable.rb
Executable file → Normal file
100
lib/common/models/wp_item/existable.rb
Executable file → Normal file
@@ -1,50 +1,50 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpItem
|
class WpItem
|
||||||
module Existable
|
module Existable
|
||||||
|
|
||||||
# Check the existence of the WpItem
|
# Check the existence of the WpItem
|
||||||
# If the response is supplied, it's used for the verification
|
# If the response is supplied, it's used for the verification
|
||||||
# Otherwise a new request is done
|
# Otherwise a new request is done
|
||||||
#
|
#
|
||||||
# @param [ Hash ] options See exists_from_response?
|
# @param [ Hash ] options See exists_from_response?
|
||||||
# @param [ Typhoeus::Response ] response
|
# @param [ Typhoeus::Response ] response
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def exists?(options = {}, response = nil)
|
def exists?(options = {}, response = nil)
|
||||||
unless response
|
unless response
|
||||||
response = Browser.get(url)
|
response = Browser.get(url)
|
||||||
end
|
end
|
||||||
exists_from_response?(response, options)
|
exists_from_response?(response, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
# @param [ Typhoeus::Response ] response
|
# @param [ Typhoeus::Response ] response
|
||||||
# @param [ options ] options
|
# @param [ options ] options
|
||||||
#
|
#
|
||||||
# @option options [ Hash ] :error_404_hash The hash of the error 404 page
|
# @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 ] :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
|
# @option options [ Hash ] :exclude_content A regexp with the pattern to exclude from the body of the response
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def exists_from_response?(response, options = {})
|
def exists_from_response?(response, options = {})
|
||||||
# 301 included as some items do a self-redirect
|
# 301 included as some items do a self-redirect
|
||||||
# Redirects to the 404 and homepage should be ignored (unless dynamic content is used)
|
# Redirects to the 404 and homepage should be ignored (unless dynamic content is used)
|
||||||
# by the page hashes (error_404_hash & homepage_hash)
|
# by the page hashes (error_404_hash & homepage_hash)
|
||||||
if [200, 401, 403, 301].include?(response.code)
|
if [200, 401, 403, 301].include?(response.code)
|
||||||
if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash])
|
if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash])
|
||||||
if options[:exclude_content]
|
if options[:exclude_content]
|
||||||
unless response.body.match(options[:exclude_content])
|
unless response.body.match(options[:exclude_content])
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
37
lib/common/models/wp_item/findable.rb
Executable file → Normal file
37
lib/common/models/wp_item/findable.rb
Executable file → Normal file
@@ -1,19 +1,18 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpItem
|
class WpItem
|
||||||
attr_reader :found_from
|
attr_reader :found_from
|
||||||
|
|
||||||
# Sets the found_from attribute
|
# Sets the found_from attribute
|
||||||
#
|
#
|
||||||
# @param [ String ] method The method which found the WpItem
|
# @param [ String ] method The method which found the WpItem
|
||||||
#
|
#
|
||||||
# @return [ void ]
|
# @return [ void ]
|
||||||
def found_from=(method)
|
def found_from=(method)
|
||||||
found = method[%r{find_from_(.*)}, 1]
|
@found_from = method.to_s.gsub(/find_from_/, '').gsub(/_/, ' ')
|
||||||
@found_from = found.gsub('_', ' ') if found
|
end
|
||||||
end
|
|
||||||
|
module Findable
|
||||||
module Findable
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|||||||
@@ -5,20 +5,25 @@ class WpItem
|
|||||||
|
|
||||||
# @return [ Void ]
|
# @return [ Void ]
|
||||||
def output(verbose = false)
|
def output(verbose = false)
|
||||||
|
outdated = VersionCompare.lesser?(version, latest_version) if latest_version
|
||||||
|
|
||||||
puts
|
puts
|
||||||
puts "#{info('[+]')} Name: #{self}" #this will also output the version number if detected
|
puts info("Name: #{self}") #this will also output the version number if detected
|
||||||
|
puts " | Latest version: #{latest_version} #{'(up to date)' if version}" if latest_version && !outdated
|
||||||
|
puts " | Last updated: #{last_updated}" if last_updated
|
||||||
puts " | Location: #{url}"
|
puts " | Location: #{url}"
|
||||||
#puts " | WordPress: #{wordpress_url}" if wordpress_org_item?
|
|
||||||
puts " | Readme: #{readme_url}" if has_readme?
|
puts " | Readme: #{readme_url}" if has_readme?
|
||||||
puts " | Changelog: #{changelog_url}" if has_changelog?
|
puts " | Changelog: #{changelog_url}" if has_changelog?
|
||||||
puts "#{warning('[!]')} Directory listing is enabled: #{url}" if has_directory_listing?
|
puts warning("The version is out of date, the latest version is #{latest_version}") if latest_version && outdated
|
||||||
puts "#{warning('[!]')} An error_log file has been found: #{error_log_url}" if has_error_log?
|
|
||||||
|
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)
|
additional_output(verbose) if respond_to?(:additional_output)
|
||||||
|
|
||||||
if version.nil? && vulnerabilities.length > 0
|
if version.nil? && vulnerabilities.length > 0
|
||||||
puts
|
puts
|
||||||
puts "#{warning('[+]')} We could not determine a version so all vulnerabilities are printed out"
|
puts warning('We could not determine a version so all vulnerabilities are printed out')
|
||||||
end
|
end
|
||||||
|
|
||||||
vulnerabilities.output
|
vulnerabilities.output
|
||||||
|
|||||||
2
lib/common/models/wp_item/versionable.rb
Executable file → Normal file
2
lib/common/models/wp_item/versionable.rb
Executable file → Normal file
@@ -22,7 +22,7 @@ class WpItem
|
|||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def to_s
|
def to_s
|
||||||
item_version = self.version
|
item_version = self.version
|
||||||
"#@name#{' - v' + item_version.strip if item_version}"
|
"#{@name}#{' - v' + item_version.strip if item_version}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Extracts the version number from a given string/body
|
# Extracts the version number from a given string/body
|
||||||
|
|||||||
95
lib/common/models/wp_item/vulnerable.rb
Executable file → Normal file
95
lib/common/models/wp_item/vulnerable.rb
Executable file → Normal file
@@ -1,51 +1,44 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpItem
|
class WpItem
|
||||||
module Vulnerable
|
module Vulnerable
|
||||||
attr_accessor :vulns_file, :identifier
|
attr_accessor :db_file, :identifier
|
||||||
|
|
||||||
# Get the vulnerabilities associated to the WpItem
|
# Get the vulnerabilities associated to the WpItem
|
||||||
# Filters out already fixed vulnerabilities
|
# Filters out already fixed vulnerabilities
|
||||||
#
|
#
|
||||||
# @return [ Vulnerabilities ]
|
# @return [ Vulnerabilities ]
|
||||||
def vulnerabilities
|
def vulnerabilities
|
||||||
json = json(vulns_file)
|
return @vulnerabilities if @vulnerabilities
|
||||||
vulnerabilities = Vulnerabilities.new
|
|
||||||
|
@vulnerabilities = Vulnerabilities.new
|
||||||
json.each do |item|
|
|
||||||
asset = item[identifier]
|
[*db_data['vulnerabilities']].each do |vulnerability|
|
||||||
|
vulnerability = Vulnerability.load_from_json_item(vulnerability)
|
||||||
next unless asset
|
@vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
||||||
|
end
|
||||||
asset['vulnerabilities'].each do |vulnerability|
|
|
||||||
vulnerability = Vulnerability.load_from_json_item(vulnerability)
|
@vulnerabilities
|
||||||
vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
end
|
||||||
end
|
|
||||||
|
def vulnerable?
|
||||||
break # No need to iterate any further
|
vulnerabilities.empty? ? false : true
|
||||||
end
|
end
|
||||||
|
|
||||||
vulnerabilities
|
# Checks if a item is vulnerable to a specific vulnerability
|
||||||
end
|
#
|
||||||
|
# @param [ Vulnerability ] vuln Vulnerability to check the item against
|
||||||
def vulnerable?
|
#
|
||||||
vulnerabilities.empty? ? false : true
|
# @return [ Boolean ]
|
||||||
end
|
def vulnerable_to?(vuln)
|
||||||
|
if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
|
||||||
# Checks if a item is vulnerable to a specific vulnerability
|
unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version)
|
||||||
#
|
return true
|
||||||
# @param [ Vulnerability ] vuln Vulnerability to check the item against
|
end
|
||||||
#
|
else
|
||||||
# @return [ Boolean ]
|
return true
|
||||||
def vulnerable_to?(vuln)
|
end
|
||||||
if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
|
return false
|
||||||
unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version)
|
end
|
||||||
return true
|
end
|
||||||
end
|
end
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
33
lib/common/models/wp_plugin.rb
Executable file → Normal file
33
lib/common/models/wp_plugin.rb
Executable file → Normal file
@@ -1,17 +1,16 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'wp_plugin/vulnerable'
|
class WpPlugin < WpItem
|
||||||
|
# Sets the @uri
|
||||||
class WpPlugin < WpItem
|
#
|
||||||
include WpPlugin::Vulnerable
|
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||||
|
#
|
||||||
# Sets the @uri
|
# @return [ void ]
|
||||||
#
|
def forge_uri(target_base_uri)
|
||||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
@uri = target_base_uri.merge("#{wp_plugins_dir}/#{url_encode(name)}/")
|
||||||
#
|
end
|
||||||
# @return [ void ]
|
|
||||||
def forge_uri(target_base_uri)
|
def db_file
|
||||||
@uri = target_base_uri.merge(URI.encode(wp_plugins_dir + '/' + name + '/'))
|
@db_file ||= PLUGINS_FILE
|
||||||
end
|
end
|
||||||
|
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
|
|
||||||
73
lib/common/models/wp_theme.rb
Executable file → Normal file
73
lib/common/models/wp_theme.rb
Executable file → Normal file
@@ -1,36 +1,37 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'wp_theme/findable'
|
require 'wp_theme/findable'
|
||||||
require 'wp_theme/versionable'
|
require 'wp_theme/versionable'
|
||||||
require 'wp_theme/vulnerable'
|
require 'wp_theme/info'
|
||||||
require 'wp_theme/info'
|
require 'wp_theme/output'
|
||||||
require 'wp_theme/output'
|
require 'wp_theme/childtheme'
|
||||||
require 'wp_theme/childtheme'
|
|
||||||
|
class WpTheme < WpItem
|
||||||
class WpTheme < WpItem
|
extend WpTheme::Findable
|
||||||
extend WpTheme::Findable
|
include WpTheme::Versionable
|
||||||
include WpTheme::Versionable
|
include WpTheme::Info
|
||||||
include WpTheme::Vulnerable
|
include WpTheme::Output
|
||||||
include WpTheme::Info
|
include WpTheme::Childtheme
|
||||||
include WpTheme::Output
|
|
||||||
include WpTheme::Childtheme
|
attr_accessor :referenced_url
|
||||||
|
|
||||||
attr_accessor :referenced_url
|
def allowed_options; super << :referenced_url end
|
||||||
|
|
||||||
def allowed_options; super << :referenced_url end
|
# Sets the @uri
|
||||||
|
#
|
||||||
# Sets the @uri
|
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||||
#
|
#
|
||||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
# @return [ void ]
|
||||||
#
|
def forge_uri(target_base_uri)
|
||||||
# @return [ void ]
|
@uri = target_base_uri.merge("#{wp_content_dir}/themes/#{url_encode(name)}/")
|
||||||
def forge_uri(target_base_uri)
|
end
|
||||||
@uri = target_base_uri.merge(URI.encode(wp_content_dir + '/themes/' + name + '/'))
|
|
||||||
end
|
# @return [ String ] The url to the theme stylesheet
|
||||||
|
def style_url
|
||||||
# @return [ String ] The url to the theme stylesheet
|
@uri.merge('style.css').to_s
|
||||||
def style_url
|
end
|
||||||
@uri.merge('style.css').to_s
|
|
||||||
end
|
def db_file
|
||||||
|
@db_file ||= THEMES_FILE
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class WpTheme < WpItem
|
|||||||
|
|
||||||
def get_parent_theme_style_url
|
def get_parent_theme_style_url
|
||||||
if is_child_theme?
|
if is_child_theme?
|
||||||
return style_url.sub("/#{name}/style.css", "/#@theme_template/style.css")
|
return style_url.sub("/#{name}/style.css", "/#{@theme_template}/style.css")
|
||||||
end
|
end
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|||||||
134
lib/common/models/wp_theme/findable.rb
Executable file → Normal file
134
lib/common/models/wp_theme/findable.rb
Executable file → Normal file
@@ -1,70 +1,64 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpTheme < WpItem
|
class WpTheme < WpItem
|
||||||
module Findable
|
module Findable
|
||||||
|
|
||||||
# Find the main theme of the blog
|
# Find the main theme of the blog
|
||||||
#
|
#
|
||||||
# @param [ URI ] target_uri
|
# @param [ URI ] target_uri
|
||||||
#
|
#
|
||||||
# @return [ WpTheme ]
|
# @return [ WpTheme ]
|
||||||
def find(target_uri)
|
def find(target_uri)
|
||||||
methods.grep(/^find_from_/).each do |method|
|
methods.grep(/^find_from_/).each do |method|
|
||||||
if wp_theme = self.send(method, target_uri)
|
if wp_theme = self.send(method, target_uri)
|
||||||
wp_theme.found_from = method
|
wp_theme.found_from = method.to_s
|
||||||
|
|
||||||
return wp_theme
|
return wp_theme
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
# Discover the wordpress theme by parsing the css link rel
|
# Discover the wordpress theme by parsing the css link rel
|
||||||
#
|
#
|
||||||
# @param [ URI ] target_uri
|
# @param [ URI ] target_uri
|
||||||
#
|
#
|
||||||
# @return [ WpTheme ]
|
# @return [ WpTheme ]
|
||||||
def find_from_css_link(target_uri)
|
def find_from_css_link(target_uri)
|
||||||
response = Browser.get_and_follow_location(target_uri.to_s)
|
response = Browser.get_and_follow_location(target_uri.to_s)
|
||||||
|
|
||||||
# https + domain is optional because of relative links
|
# https + domain is optional because of relative links
|
||||||
matches = /(?:https?:\/\/[^"']+)?\/([^\/]+)\/themes\/([^"'\/]+)[^"']*\/style.css/i.match(response.body)
|
return unless response.body =~ %r{(?:https?://[^"']+/)?([^/\s]+)/themes/([^"'/]+)[^"']*/style.css}i
|
||||||
if matches
|
|
||||||
return new(
|
new(
|
||||||
target_uri,
|
target_uri,
|
||||||
{
|
name: Regexp.last_match[2],
|
||||||
name: matches[2],
|
referenced_url: Regexp.last_match[0],
|
||||||
referenced_url: matches[0],
|
wp_content_dir: Regexp.last_match[1]
|
||||||
wp_content_dir: matches[1]
|
)
|
||||||
}
|
end
|
||||||
)
|
|
||||||
end
|
# @param [ URI ] target_uri
|
||||||
end
|
#
|
||||||
|
# @return [ WpTheme ]
|
||||||
# @param [ URI ] target_uri
|
def find_from_wooframework(target_uri)
|
||||||
#
|
body = Browser.get(target_uri.to_s).body
|
||||||
# @return [ WpTheme ]
|
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
|
||||||
def find_from_wooframework(target_uri)
|
|
||||||
body = Browser.get(target_uri.to_s).body
|
if matches = regexp.match(body)
|
||||||
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
|
woo_theme_name = matches[1]
|
||||||
|
woo_theme_version = matches[2]
|
||||||
|
#woo_framework_version = matches[3] # Not used at this time
|
||||||
if matches = regexp.match(body)
|
|
||||||
woo_theme_name = matches[1]
|
return new(
|
||||||
woo_theme_version = matches[2]
|
target_uri,
|
||||||
#woo_framework_version = matches[3] # Not used at this time
|
name: woo_theme_name,
|
||||||
|
version: woo_theme_version
|
||||||
return new(
|
)
|
||||||
target_uri,
|
end
|
||||||
{
|
end
|
||||||
name: woo_theme_name,
|
|
||||||
version: woo_theme_version
|
end
|
||||||
}
|
end
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -6,20 +6,20 @@ class WpTheme
|
|||||||
# @return [ Void ]
|
# @return [ Void ]
|
||||||
def additional_output(verbose = false)
|
def additional_output(verbose = false)
|
||||||
parse_style
|
parse_style
|
||||||
|
|
||||||
theme_desc = verbose ? @theme_description : truncate(@theme_description, 100)
|
theme_desc = verbose ? @theme_description : truncate(@theme_description, 100)
|
||||||
puts " | Style URL: #{style_url}"
|
puts " | Style URL: #{style_url}"
|
||||||
puts " | Referenced style.css: #{referenced_url}" if referenced_url && referenced_url != style_url
|
puts " | Referenced style.css: #{referenced_url}" if referenced_url && referenced_url != style_url
|
||||||
puts " | Theme Name: #@theme_name" if @theme_name
|
puts " | Theme Name: #{@theme_name}" if @theme_name
|
||||||
puts " | Theme URI: #@theme_uri" if @theme_uri
|
puts " | Theme URI: #{@theme_uri}" if @theme_uri
|
||||||
puts " | Description: #{theme_desc}"
|
puts " | Description: #{theme_desc}" if theme_desc
|
||||||
puts " | Author: #@theme_author" if @theme_author
|
puts " | Author: #{@theme_author}" if @theme_author
|
||||||
puts " | Author URI: #@theme_author_uri" if @theme_author_uri
|
puts " | Author URI: #{@theme_author_uri}" if @theme_author_uri
|
||||||
puts " | Template: #@theme_template" if @theme_template and verbose
|
puts " | Template: #{@theme_template}" if @theme_template and verbose
|
||||||
puts " | License: #@theme_license" if @theme_license and verbose
|
puts " | License: #{@theme_license}" if @theme_license and verbose
|
||||||
puts " | License URI: #@theme_license_uri" if @theme_license_uri and verbose
|
puts " | License URI: #{@theme_license_uri}" if @theme_license_uri and verbose
|
||||||
puts " | Tags: #@theme_tags" if @theme_tags and verbose
|
puts " | Tags: #{@theme_tags}" if @theme_tags and verbose
|
||||||
puts " | Text Domain: #@theme_text_domain" if @theme_text_domain and verbose
|
puts " | Text Domain: #{@theme_text_domain}" if @theme_text_domain and verbose
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
18
lib/common/models/wp_theme/versionable.rb
Executable file → Normal file
18
lib/common/models/wp_theme/versionable.rb
Executable file → Normal file
@@ -1,9 +1,9 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpTheme < WpItem
|
class WpTheme < WpItem
|
||||||
module Versionable
|
module Versionable
|
||||||
def version
|
def version
|
||||||
@version ||= Browser.get(style_url).body[%r{Version:\s*([^\s]+)}i, 1]
|
@version ||= Browser.get(style_url).body[%r{Version:\s*(?!trunk)([0-9a-z\.-]+)}i, 1]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
|
||||||
40
lib/common/models/wp_timthumb.rb
Executable file → Normal file
40
lib/common/models/wp_timthumb.rb
Executable file → Normal file
@@ -1,20 +1,20 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'wp_timthumb/versionable'
|
require 'wp_timthumb/versionable'
|
||||||
require 'wp_timthumb/existable'
|
require 'wp_timthumb/existable'
|
||||||
require 'wp_timthumb/output'
|
require 'wp_timthumb/output'
|
||||||
require 'wp_timthumb/vulnerable'
|
require 'wp_timthumb/vulnerable'
|
||||||
|
|
||||||
class WpTimthumb < WpItem
|
class WpTimthumb < WpItem
|
||||||
include WpTimthumb::Versionable
|
include WpTimthumb::Versionable
|
||||||
include WpTimthumb::Existable
|
include WpTimthumb::Existable
|
||||||
include WpTimthumb::Output
|
include WpTimthumb::Output
|
||||||
include WpTimthumb::Vulnerable
|
include WpTimthumb::Vulnerable
|
||||||
|
|
||||||
# @param [ WpTimthumb ] other
|
# @param [ WpTimthumb ] other
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def ==(other)
|
def ==(other)
|
||||||
url == other.url
|
url == other.url
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class WpTimthumb < WpItem
|
|||||||
|
|
||||||
def output(verbose = false)
|
def output(verbose = false)
|
||||||
puts
|
puts
|
||||||
puts "#{info('[+]')} #{self}" #this will also output the version number if detected
|
puts info("#{self}") #this will also output the version number if detected
|
||||||
|
|
||||||
vulnerabilities.output
|
vulnerabilities.output
|
||||||
end
|
end
|
||||||
|
|||||||
48
lib/common/models/wp_timthumb/versionable.rb
Executable file → Normal file
48
lib/common/models/wp_timthumb/versionable.rb
Executable file → Normal file
@@ -1,24 +1,24 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpTimthumb < WpItem
|
class WpTimthumb < WpItem
|
||||||
module Versionable
|
module Versionable
|
||||||
|
|
||||||
# Get the version from the body of an invalid request
|
# Get the version from the body of an invalid request
|
||||||
# See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426
|
# See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426
|
||||||
#
|
#
|
||||||
# @return [ String ] The version
|
# @return [ String ] The version
|
||||||
def version
|
def version
|
||||||
unless @version
|
unless @version
|
||||||
response = Browser.get(url)
|
response = Browser.get(url)
|
||||||
@version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1]
|
@version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1]
|
||||||
end
|
end
|
||||||
@version
|
@version
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def to_s
|
def to_s
|
||||||
"#{url}#{ ' v' + version if version}"
|
"#{url}#{ ' v' + version if version}"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class WpTimthumb < WpItem
|
|||||||
end
|
end
|
||||||
|
|
||||||
def check_rce_132
|
def check_rce_132
|
||||||
return rce_132_vuln unless VersionCompare.lesser_or_equal?('1.33', version)
|
rce_132_vuln unless VersionCompare.lesser_or_equal?('1.33', version)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Vulnerable versions : > 1.35 (or >= 2.0) and < 2.8.14
|
# Vulnerable versions : > 1.35 (or >= 2.0) and < 2.8.14
|
||||||
@@ -24,7 +24,7 @@ class WpTimthumb < WpItem
|
|||||||
|
|
||||||
response = Browser.get(uri.merge('?webshot=1&src=http://' + default_allowed_domains.sample))
|
response = Browser.get(uri.merge('?webshot=1&src=http://' + default_allowed_domains.sample))
|
||||||
|
|
||||||
return rce_webshot_vuln unless response.body =~ /WEBSHOT_ENABLED == true/
|
rce_webshot_vuln unless response.body =~ /WEBSHOT_ENABLED == true/
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<String> ] The default allowed domains (between the 2.0 and 2.8.13)
|
# @return [ Array<String> ] The default allowed domains (between the 2.0 and 2.8.13)
|
||||||
|
|||||||
162
lib/common/models/wp_user.rb
Executable file → Normal file
162
lib/common/models/wp_user.rb
Executable file → Normal file
@@ -1,81 +1,81 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'wp_user/existable'
|
require 'wp_user/existable'
|
||||||
require 'wp_user/brute_forcable'
|
require 'wp_user/brute_forcable'
|
||||||
|
|
||||||
class WpUser < WpItem
|
class WpUser < WpItem
|
||||||
include WpUser::Existable
|
include WpUser::Existable
|
||||||
include WpUser::BruteForcable
|
include WpUser::BruteForcable
|
||||||
|
|
||||||
attr_accessor :id, :login, :display_name, :password
|
attr_accessor :id, :login, :display_name, :password
|
||||||
|
|
||||||
# @return [ Array<Symbol> ]
|
# @return [ Array<Symbol> ]
|
||||||
def allowed_options; [:id, :login, :display_name, :password] end
|
def allowed_options; [:id, :login, :display_name, :password] end
|
||||||
|
|
||||||
# @return [ URI ] The uri to the author page
|
# @return [ URI ] The uri to the author page
|
||||||
def uri
|
def uri
|
||||||
if id
|
if id
|
||||||
return @uri.merge("?author=#{id}")
|
@uri.merge("?author=#{id}")
|
||||||
else
|
else
|
||||||
raise 'The id is nil'
|
raise 'The id is nil'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def login_url
|
def login_url
|
||||||
unless @login_url
|
unless @login_url
|
||||||
@login_url = @uri.merge('wp-login.php').to_s
|
@login_url = @uri.merge('wp-login.php').to_s
|
||||||
|
|
||||||
# Let's check if the login url is redirected (to https url for example)
|
# Let's check if the login url is redirected (to https url for example)
|
||||||
if redirection = redirection(@login_url)
|
if redirection = redirection(@login_url)
|
||||||
@login_url = redirection
|
@login_url = redirection
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@login_url
|
@login_url
|
||||||
end
|
end
|
||||||
|
|
||||||
def redirection(url)
|
def redirection(url)
|
||||||
redirection = nil
|
redirection = nil
|
||||||
response = Browser.get(url)
|
response = Browser.get(url)
|
||||||
|
|
||||||
if response.code == 301 || response.code == 302
|
if response.code == 301 || response.code == 302
|
||||||
redirection = response.headers_hash['location']
|
redirection = response.headers_hash['location']
|
||||||
|
|
||||||
# Let's check if there is a redirection in the redirection
|
# Let's check if there is a redirection in the redirection
|
||||||
if other_redirection = redirection(redirection)
|
if other_redirection = redirection(redirection)
|
||||||
redirection = other_redirection
|
redirection = other_redirection
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
redirection
|
redirection
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def to_s
|
def to_s
|
||||||
s = "#{id}"
|
s = "#{id}"
|
||||||
s << " | #{login}" if login
|
s << " | #{login}" if login
|
||||||
s << " | #{display_name}" if display_name
|
s << " | #{display_name}" if display_name
|
||||||
s
|
s
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ WpUser ] other
|
# @param [ WpUser ] other
|
||||||
def <=>(other)
|
def <=>(other)
|
||||||
id <=> other.id
|
id <=> other.id
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ WpUser ] other
|
# @param [ WpUser ] other
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def ==(other)
|
def ==(other)
|
||||||
self === other
|
self === other
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ WpUser ] other
|
# @param [ WpUser ] other
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def ===(other)
|
def ===(other)
|
||||||
id === other.id && login === other.login
|
id === other.id && login === other.login
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
class WpUser < WpItem
|
class WpUser < WpItem
|
||||||
module BruteForcable
|
module BruteForcable
|
||||||
|
|
||||||
|
attr_reader :progress_bar
|
||||||
|
|
||||||
# Brute force the user with the wordlist supplied
|
# Brute force the user with the wordlist supplied
|
||||||
#
|
#
|
||||||
# It can take a long time to queue 2 million requests,
|
# It can take a long time to queue 2 million requests,
|
||||||
@@ -25,7 +27,8 @@ class WpUser < WpItem
|
|||||||
hydra = browser.hydra
|
hydra = browser.hydra
|
||||||
queue_count = 0
|
queue_count = 0
|
||||||
found = false
|
found = false
|
||||||
progress_bar = self.progress_bar(count_file_lines(wordlist)+1, options)
|
|
||||||
|
create_progress_bar(count_file_lines(wordlist)+1, options)
|
||||||
|
|
||||||
File.open(wordlist).each do |password|
|
File.open(wordlist).each do |password|
|
||||||
password.chomp!
|
password.chomp!
|
||||||
@@ -34,7 +37,7 @@ class WpUser < WpItem
|
|||||||
# Generate a random one on each request
|
# Generate a random one on each request
|
||||||
unless redirect_url
|
unless redirect_url
|
||||||
random = (0...8).map { 65.+(rand(26)).chr }.join
|
random = (0...8).map { 65.+(rand(26)).chr }.join
|
||||||
redirect_url = "#@uri#{random}/"
|
redirect_url = "#{@uri}#{random}/"
|
||||||
end
|
end
|
||||||
|
|
||||||
request = login_request(password, redirect_url)
|
request = login_request(password, redirect_url)
|
||||||
@@ -42,7 +45,7 @@ class WpUser < WpItem
|
|||||||
request.on_complete do |response|
|
request.on_complete do |response|
|
||||||
progress_bar.progress += 1 if options[:show_progression] && !found
|
progress_bar.progress += 1 if options[:show_progression] && !found
|
||||||
|
|
||||||
puts "\n Trying Username : #{login} Password : #{password}" if options[:verbose]
|
progress_bar.log(" Trying Username: #{login} Password: #{password}") if options[:verbose]
|
||||||
|
|
||||||
if valid_password?(response, password, redirect_url, options)
|
if valid_password?(response, password, redirect_url, options)
|
||||||
found = true
|
found = true
|
||||||
@@ -57,7 +60,7 @@ class WpUser < WpItem
|
|||||||
if queue_count >= browser.max_threads
|
if queue_count >= browser.max_threads
|
||||||
hydra.run
|
hydra.run
|
||||||
queue_count = 0
|
queue_count = 0
|
||||||
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
|
progress_bar.log(" Sent #{browser.max_threads} request/s ...") if options[:verbose]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -66,14 +69,14 @@ class WpUser < WpItem
|
|||||||
puts if options[:show_progression] # mandatory to avoid the output of the progressbar to be overriden
|
puts if options[:show_progression] # mandatory to avoid the output of the progressbar to be overriden
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Integer ] targets_size
|
# @param [ Integer ] passwords_size
|
||||||
# @param [ Hash ] options
|
# @param [ Hash ] options
|
||||||
#
|
#
|
||||||
# @return [ ProgressBar ]
|
# @return [ ProgressBar ]
|
||||||
# :nocov:
|
# :nocov:
|
||||||
def progress_bar(passwords_size, options)
|
def create_progress_bar(passwords_size, options)
|
||||||
if options[:show_progression]
|
if options[:show_progression]
|
||||||
ProgressBar.create(
|
@progress_bar = ProgressBar.create(
|
||||||
format: '%t %a <%B> (%c / %C) %P%% %e',
|
format: '%t %a <%B> (%c / %C) %P%% %e',
|
||||||
title: " Brute Forcing '#{login}'",
|
title: " Brute Forcing '#{login}'",
|
||||||
total: passwords_size
|
total: passwords_size
|
||||||
@@ -107,20 +110,20 @@ class WpUser < WpItem
|
|||||||
progression = "#{info('[SUCCESS]')} Login : #{login} Password : #{password}\n\n"
|
progression = "#{info('[SUCCESS]')} Login : #{login} Password : #{password}\n\n"
|
||||||
valid = true
|
valid = true
|
||||||
elsif response.body =~ /login_error/i
|
elsif response.body =~ /login_error/i
|
||||||
verbose = "\n Incorrect login and/or password."
|
verbose = "Incorrect login and/or password."
|
||||||
elsif response.timed_out?
|
elsif response.timed_out?
|
||||||
progression = "#{critical('ERROR:')} Request timed out."
|
progression = critical('ERROR: Request timed out.')
|
||||||
elsif response.code == 0
|
elsif response.code == 0
|
||||||
progression = "#{critical('ERROR:')} No response from remote server. WAF/IPS?"
|
progression = critical("ERROR: No response from remote server. WAF/IPS? (#{response.return_message})")
|
||||||
elsif response.code.to_s =~ /^50/
|
elsif response.code.to_s =~ /^50/
|
||||||
progression = "#{critical('ERROR:')} Server error, try reducing the number of threads."
|
progression = critical('ERROR: Server error, try reducing the number of threads or use the --throttle option.')
|
||||||
else
|
else
|
||||||
progression = "#{critical('ERROR:')} We received an unknown response for #{password}..."
|
progression = critical("ERROR: We received an unknown response for #{password}...")
|
||||||
verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
|
verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
puts "\n " + progression if progression && options[:show_progression]
|
progress_bar.log(" #{progression}") if progression && options[:show_progression]
|
||||||
puts verbose if verbose && options[:verbose]
|
progress_bar.log(" #{verbose}") if verbose && options[:verbose]
|
||||||
|
|
||||||
valid || false
|
valid || false
|
||||||
end
|
end
|
||||||
|
|||||||
169
lib/common/models/wp_user/existable.rb
Executable file → Normal file
169
lib/common/models/wp_user/existable.rb
Executable file → Normal file
@@ -1,83 +1,86 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpUser < WpItem
|
class WpUser < WpItem
|
||||||
module Existable
|
module Existable
|
||||||
|
|
||||||
# @param [ Typhoeus::Response ] response
|
# @param [ Typhoeus::Response ] response
|
||||||
# @param [ Hash ] options
|
# @param [ Hash ] options
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def exists_from_response?(response, options = {})
|
def exists_from_response?(response, options = {})
|
||||||
load_from_response(response)
|
load_from_response(response)
|
||||||
|
|
||||||
@login ? true : false
|
@login ? true : false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Load the login and display_name from the response
|
# Load the login and display_name from the response
|
||||||
#
|
#
|
||||||
# @param [ Typhoeus::Response ] response
|
# @param [ Typhoeus::Response ] response
|
||||||
#
|
#
|
||||||
# @return [ void ]
|
# @return [ void ]
|
||||||
def load_from_response(response)
|
def load_from_response(response)
|
||||||
if response.code == 301 # login in location?
|
if response.code == 301 # login in location?
|
||||||
location = response.headers_hash['Location']
|
location = response.headers_hash['Location']
|
||||||
|
|
||||||
return if location.nil? || location.empty?
|
return if location.nil? || location.empty?
|
||||||
|
|
||||||
@login = Existable.login_from_author_pattern(location)
|
@login = Existable.login_from_author_pattern(location)
|
||||||
@display_name = Existable.display_name_from_body(
|
@display_name = Existable.display_name_from_body(
|
||||||
Browser.get(location).body
|
Browser.get(location).body
|
||||||
)
|
)
|
||||||
elsif response.code == 200 # login in body?
|
elsif response.code == 200 # login in body?
|
||||||
@login = Existable.login_from_body(response.body)
|
@login = Existable.login_from_body(response.body)
|
||||||
@display_name = Existable.display_name_from_body(response.body)
|
@display_name = Existable.display_name_from_body(response.body)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
private :load_from_response
|
private :load_from_response
|
||||||
|
|
||||||
# @param [ String ] text
|
# @param [ String ] text
|
||||||
#
|
#
|
||||||
# @return [ String ] The login
|
# @return [ String ] The login
|
||||||
def self.login_from_author_pattern(text)
|
def self.login_from_author_pattern(text)
|
||||||
text[%r{/author/([^/\b]+)/?}i, 1]
|
return unless text =~ %r{/author/([^/\b"']+)/?}i
|
||||||
end
|
|
||||||
|
Regexp.last_match[1].force_encoding('UTF-8')
|
||||||
# @param [ String ] body
|
end
|
||||||
#
|
|
||||||
# @return [ String ] The login
|
# @param [ String ] body
|
||||||
def self.login_from_body(body)
|
#
|
||||||
# Feed URL with Permalinks
|
# @return [ String ] The login
|
||||||
login = WpUser::Existable.login_from_author_pattern(body)
|
def self.login_from_body(body)
|
||||||
|
# Feed URL with Permalinks
|
||||||
unless login
|
login = WpUser::Existable.login_from_author_pattern(body)
|
||||||
# No Permalinks
|
|
||||||
login = body[%r{<body class="archive author author-([^\s]+)[ "]}i, 1]
|
unless login
|
||||||
end
|
# No Permalinks
|
||||||
|
login = body[%r{<body class="archive author author-([^\s]+)[ "]}i, 1]
|
||||||
login
|
login ? login.force_encoding('UTF-8') : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# @note Some bodies are encoded in ASCII-8BIT, and Nokogiri doesn't support it
|
login
|
||||||
# So it's forced to UTF-8 when this encoding is detected
|
end
|
||||||
#
|
|
||||||
# @param [ String ] body
|
# @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
|
||||||
# @return [ String ] The display_name
|
#
|
||||||
def self.display_name_from_body(body)
|
# @param [ String ] body
|
||||||
if title_tag = body[%r{<title>([^<]+)</title>}i, 1]
|
#
|
||||||
title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT
|
# @return [ String ] The display_name
|
||||||
title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s
|
def self.display_name_from_body(body)
|
||||||
# & are not decoded with Nokogiri
|
if title_tag = body[%r{<title>([^<]+)</title>}i, 1]
|
||||||
title_tag.gsub!('&', '&')
|
title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT
|
||||||
|
title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s
|
||||||
# replace UTF chars like » with dummy character
|
# & are not decoded with Nokogiri
|
||||||
title_tag.gsub!(/&#(\d+);/, '|')
|
title_tag.gsub!('&', '&')
|
||||||
|
|
||||||
name = title_tag[%r{([^|«»]+) }, 1]
|
# replace UTF chars like » with dummy character
|
||||||
|
title_tag.gsub!(/&#(\d+);/, '|')
|
||||||
return name.strip if name
|
|
||||||
end
|
name = title_tag[%r{([^|«»]+) }, 1]
|
||||||
end
|
|
||||||
|
return name.strip if name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
84
lib/common/models/wp_version.rb
Executable file → Normal file
84
lib/common/models/wp_version.rb
Executable file → Normal file
@@ -1,33 +1,51 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require 'wp_version/findable'
|
require 'wp_version/findable'
|
||||||
require 'wp_version/vulnerable'
|
require 'wp_version/output'
|
||||||
require 'wp_version/output'
|
|
||||||
|
class WpVersion < WpItem
|
||||||
class WpVersion < WpItem
|
extend WpVersion::Findable
|
||||||
|
include WpVersion::Output
|
||||||
extend WpVersion::Findable
|
|
||||||
include WpVersion::Vulnerable
|
# The version number
|
||||||
include WpVersion::Output
|
attr_accessor :number, :metadata
|
||||||
|
alias_method :version, :number # Needed to have the right behaviour in Vulnerable#vulnerable_to?
|
||||||
# The version number
|
|
||||||
attr_accessor :number
|
# @return [ Array ]
|
||||||
|
def allowed_options; super << :number << :found_from end
|
||||||
# @return [ Array ]
|
|
||||||
def allowed_options; super << :number << :found_from end
|
def identifier
|
||||||
|
@identifier ||= number
|
||||||
# @param [ WpVersion ] other
|
end
|
||||||
#
|
|
||||||
# @return [ Boolean ]
|
def db_file
|
||||||
def ==(other)
|
@db_file ||= WORDPRESSES_FILE
|
||||||
number == other.number
|
end
|
||||||
end
|
|
||||||
|
# @param [ WpVersion ] other
|
||||||
# @return [ Array<String> ] All the stable versions from version_file
|
#
|
||||||
def self.all(versions_file = WP_VERSIONS_FILE)
|
# @return [ Boolean ]
|
||||||
Nokogiri.XML(File.open(versions_file)).css('version').reduce([]) do |a, node|
|
def ==(other)
|
||||||
a << node.text.to_s
|
number == other.number
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
# @return [ Array<String> ] All the stable versions from version_file
|
||||||
end
|
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 = {}
|
||||||
|
temp = json[version]
|
||||||
|
if !temp.nil?
|
||||||
|
metadata[:release_date] = temp['release_date']
|
||||||
|
metadata[:changelog_url] = temp['changelog_url']
|
||||||
|
end
|
||||||
|
metadata
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
459
lib/common/models/wp_version/findable.rb
Executable file → Normal file
459
lib/common/models/wp_version/findable.rb
Executable file → Normal file
@@ -1,222 +1,237 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpVersion < WpItem
|
class WpVersion < WpItem
|
||||||
|
|
||||||
module Findable
|
module Findable
|
||||||
|
|
||||||
# Find the version of the blog designated from target_uri
|
# Find the version of the blog designated from target_uri
|
||||||
#
|
#
|
||||||
# @param [ URI ] target_uri
|
# @param [ URI ] target_uri
|
||||||
# @param [ String ] wp_content_dir
|
# @param [ String ] wp_content_dir
|
||||||
# @param [ String ] wp_plugins_dir
|
# @param [ String ] wp_plugins_dir
|
||||||
#
|
#
|
||||||
# @return [ WpVersion ]
|
# @return [ WpVersion ]
|
||||||
def find(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
def find(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||||
methods.grep(/^find_from_/).each do |method|
|
versions = {}
|
||||||
|
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)
|
if method === :find_from_advanced_fingerprinting
|
||||||
else
|
version = send(method, target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||||
version = send(method, target_uri)
|
else
|
||||||
end
|
version = send(method, target_uri)
|
||||||
|
end
|
||||||
if version
|
|
||||||
return new(target_uri, number: version, found_from: method)
|
if version
|
||||||
end
|
if versions.key?(version)
|
||||||
end
|
versions[version] << method.to_s
|
||||||
nil
|
else
|
||||||
end
|
versions[version] = [ method.to_s ]
|
||||||
|
end
|
||||||
# Used to check if the version is correct: must contain at least one dot.
|
end
|
||||||
#
|
end
|
||||||
# @return [ String ]
|
|
||||||
def version_pattern
|
if versions.length > 0
|
||||||
'([^\r\n"\']+\.[^\r\n"\']+)'
|
determined_version = versions.max_by { |k, v| v.length }
|
||||||
end
|
if determined_version
|
||||||
|
return new(target_uri, number: determined_version[0], found_from: determined_version[1].join(', '))
|
||||||
protected
|
end
|
||||||
|
end
|
||||||
# Returns the first match of <pattern> in the body of the url
|
|
||||||
#
|
nil
|
||||||
# @param [ URI ] target_uri
|
end
|
||||||
# @param [ Regex ] pattern
|
|
||||||
# @param [ String ] path
|
# Used to check if the version is correct: must contain at least one dot.
|
||||||
#
|
#
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def scan_url(target_uri, pattern, path = nil)
|
def version_pattern
|
||||||
url = path ? target_uri.merge(path).to_s : target_uri.to_s
|
'([^\r\n"\',]+\.[^\r\n"\',]+)'
|
||||||
response = Browser.get_and_follow_location(url)
|
end
|
||||||
|
|
||||||
response.body[pattern, 1]
|
protected
|
||||||
end
|
|
||||||
|
# Returns the first match of <pattern> in the body of the url
|
||||||
#
|
#
|
||||||
# DO NOT Change the order of the following methods
|
# @param [ URI ] target_uri
|
||||||
# unless you know what you are doing
|
# @param [ Regex ] pattern
|
||||||
# See WpVersion.find
|
# @param [ String ] path
|
||||||
#
|
#
|
||||||
|
# @return [ String ]
|
||||||
# Attempts to find the wordpress version from,
|
def scan_url(target_uri, pattern, path = nil)
|
||||||
# the generator meta tag in the html source.
|
url = path ? target_uri.merge(path).to_s : target_uri.to_s
|
||||||
#
|
response = Browser.get_and_follow_location(url)
|
||||||
# The meta tag can be removed however it seems,
|
|
||||||
# that it is reinstated on upgrade.
|
response.body[pattern, 1]
|
||||||
#
|
end
|
||||||
# @param [ URI ] target_uri
|
|
||||||
#
|
#
|
||||||
# @return [ String ] The version number
|
# DO NOT Change the order of the following methods
|
||||||
def find_from_meta_generator(target_uri)
|
# unless you know what you are doing
|
||||||
scan_url(
|
# See WpVersion.find
|
||||||
target_uri,
|
#
|
||||||
%r{name="generator" content="wordpress #{version_pattern}"}i
|
|
||||||
)
|
# Attempts to find the wordpress version from,
|
||||||
end
|
# the generator meta tag in the html source.
|
||||||
|
#
|
||||||
# Attempts to find the WordPress version from,
|
# The meta tag can be removed however it seems,
|
||||||
# the generator tag in the RSS feed source.
|
# that it is reinstated on upgrade.
|
||||||
#
|
#
|
||||||
# @param [ URI ] target_uri
|
# @param [ URI ] target_uri
|
||||||
#
|
#
|
||||||
# @return [ String ] The version number
|
# @return [ String ] The version number
|
||||||
def find_from_rss_generator(target_uri)
|
def find_from_meta_generator(target_uri)
|
||||||
scan_url(
|
scan_url(
|
||||||
target_uri,
|
target_uri,
|
||||||
%r{<generator>http://wordpress.org/\?v=#{version_pattern}</generator>}i,
|
%r{name="generator" content="wordpress #{version_pattern}.*"}i
|
||||||
'feed/'
|
)
|
||||||
)
|
end
|
||||||
end
|
|
||||||
|
# Attempts to find the WordPress version from,
|
||||||
# Attempts to find WordPress version from,
|
# the generator tag in the RSS feed source.
|
||||||
# the generator tag in the RDF feed source.
|
#
|
||||||
#
|
# @param [ URI ] target_uri
|
||||||
# @param [ URI ] target_uri
|
#
|
||||||
#
|
# @return [ String ] The version number
|
||||||
# @return [ String ] The version number
|
def find_from_rss_generator(target_uri)
|
||||||
def find_from_rdf_generator(target_uri)
|
scan_url(
|
||||||
scan_url(
|
target_uri,
|
||||||
target_uri,
|
%r{<generator>http://wordpress.org/\?v=#{version_pattern}</generator>}i,
|
||||||
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{version_pattern}" />}i,
|
'feed/'
|
||||||
'feed/rdf/'
|
)
|
||||||
)
|
end
|
||||||
end
|
|
||||||
|
# Attempts to find WordPress version from,
|
||||||
# Attempts to find the WordPress version from,
|
# the generator tag in the RDF feed source.
|
||||||
# the generator tag in the Atom source.
|
#
|
||||||
#
|
# @param [ URI ] target_uri
|
||||||
# @param [ URI ] target_uri
|
#
|
||||||
#
|
# @return [ String ] The version number
|
||||||
# @return [ String ] The version number
|
def find_from_rdf_generator(target_uri)
|
||||||
def find_from_atom_generator(target_uri)
|
scan_url(
|
||||||
scan_url(
|
target_uri,
|
||||||
target_uri,
|
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{version_pattern}" />}i,
|
||||||
%r{<generator uri="http://wordpress.org/" version="#{version_pattern}">WordPress</generator>}i,
|
'feed/rdf/'
|
||||||
'feed/atom/'
|
)
|
||||||
)
|
end
|
||||||
end
|
|
||||||
|
# Attempts to find the WordPress version from,
|
||||||
def find_from_stylesheets_numbers(target_uri)
|
# the generator tag in the Atom source.
|
||||||
wp_versions = WpVersion.all
|
#
|
||||||
found = {}
|
# @param [ URI ] target_uri
|
||||||
pattern = /\bver=([0-9\.]+)/i
|
#
|
||||||
|
# @return [ String ] The version number
|
||||||
Nokogiri::HTML(Browser.get(target_uri.to_s).body).css('link,script').each do |tag|
|
def find_from_atom_generator(target_uri)
|
||||||
%w(href src).each do |attribute|
|
scan_url(
|
||||||
attr_value = tag.attribute(attribute).to_s
|
target_uri,
|
||||||
|
%r{<generator uri="http://wordpress.org/" version="#{version_pattern}">WordPress</generator>}i,
|
||||||
next if attr_value.nil? || attr_value.empty?
|
'feed/atom/'
|
||||||
|
)
|
||||||
uri = Addressable::URI.parse(attr_value)
|
end
|
||||||
next unless uri.query && uri.query.match(pattern)
|
|
||||||
|
# Uses data/wp_versions.xml to try to identify a
|
||||||
version = Regexp.last_match[1].to_s
|
# wordpress version.
|
||||||
|
#
|
||||||
found[version] ||= 0
|
# It does this by using client side file hashing
|
||||||
found[version] += 1
|
#
|
||||||
end
|
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
|
||||||
end
|
#
|
||||||
|
# @param [ URI ] target_uri
|
||||||
found.delete_if { |v, _| !wp_versions.include?(v) }
|
# @param [ String ] wp_content_dir
|
||||||
|
# @param [ String ] wp_plugins_dir
|
||||||
best_guess = found.sort_by(&:last).last
|
# @param [ String ] versions_xml The path to the xml containing all versions
|
||||||
# best_guess[0]: version number, [1] numbers of occurences
|
#
|
||||||
best_guess && best_guess[1] > 1 ? best_guess[0] : nil
|
# @return [ String ] The version number
|
||||||
end
|
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||||
|
xml = xml(versions_xml)
|
||||||
# Uses data/wp_versions.xml to try to identify a
|
|
||||||
# wordpress version.
|
wp_item = WpItem.new(target_uri,
|
||||||
#
|
wp_content_dir: wp_content_dir,
|
||||||
# It does this by using client side file hashing
|
wp_plugins_dir: wp_plugins_dir)
|
||||||
#
|
|
||||||
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
|
xml.xpath('//file').each do |node|
|
||||||
#
|
wp_item.path = node.attribute('src').text
|
||||||
# @param [ URI ] target_uri
|
|
||||||
# @param [ String ] wp_content_dir
|
response = Browser.get(wp_item.url)
|
||||||
# @param [ String ] wp_plugins_dir
|
md5sum = Digest::MD5.hexdigest(response.body)
|
||||||
# @param [ String ] versions_xml The path to the xml containing all versions
|
|
||||||
#
|
node.search('hash').each do |hash|
|
||||||
# @return [ String ] The version number
|
if hash.attribute('md5').text == md5sum
|
||||||
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
return hash.search('version').text
|
||||||
xml = xml(versions_xml)
|
end
|
||||||
|
end
|
||||||
# This wp_item will take care of encoding the path
|
end
|
||||||
# and replace variables like $wp-content$ & $wp-plugins$
|
nil
|
||||||
wp_item = WpItem.new(target_uri,
|
end
|
||||||
wp_content_dir: wp_content_dir,
|
|
||||||
wp_plugins_dir: wp_plugins_dir)
|
# Attempts to find the WordPress version from the readme.html file.
|
||||||
|
#
|
||||||
xml.xpath('//file').each do |node|
|
# @param [ URI ] target_uri
|
||||||
wp_item.path = node.attribute('src').text
|
#
|
||||||
|
# @return [ String ] The version number
|
||||||
response = Browser.get(wp_item.url)
|
def find_from_readme(target_uri)
|
||||||
md5sum = Digest::MD5.hexdigest(response.body)
|
scan_url(
|
||||||
|
target_uri,
|
||||||
node.search('hash').each do |hash|
|
%r{<br />\sversion #{version_pattern}}i,
|
||||||
if hash.attribute('md5').text == md5sum
|
'readme.html'
|
||||||
return hash.search('version').text
|
)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
# Attempts to find the WordPress version from the sitemap.xml file.
|
||||||
nil
|
#
|
||||||
end
|
# @param [ URI ] target_uri
|
||||||
|
#
|
||||||
# Attempts to find the WordPress version from the readme.html file.
|
# @return [ String ] The version number
|
||||||
#
|
def find_from_sitemap_generator(target_uri)
|
||||||
# @param [ URI ] target_uri
|
scan_url(
|
||||||
#
|
target_uri,
|
||||||
# @return [ String ] The version number
|
%r{generator="wordpress/#{version_pattern}"}i,
|
||||||
def find_from_readme(target_uri)
|
'sitemap.xml'
|
||||||
scan_url(
|
)
|
||||||
target_uri,
|
end
|
||||||
%r{<br />\sversion #{version_pattern}}i,
|
|
||||||
'readme.html'
|
# Attempts to find the WordPress version from the p-links-opml.php file.
|
||||||
)
|
#
|
||||||
end
|
# @param [ URI ] target_uri
|
||||||
|
#
|
||||||
# Attempts to find the WordPress version from the sitemap.xml file.
|
# @return [ String ] The version number
|
||||||
#
|
def find_from_links_opml(target_uri)
|
||||||
# @param [ URI ] target_uri
|
scan_url(
|
||||||
#
|
target_uri,
|
||||||
# @return [ String ] The version number
|
%r{generator="wordpress/#{version_pattern}"}i,
|
||||||
def find_from_sitemap_generator(target_uri)
|
'wp-links-opml.php'
|
||||||
scan_url(
|
)
|
||||||
target_uri,
|
end
|
||||||
%r{generator="wordpress/#{version_pattern}"}i,
|
|
||||||
'sitemap.xml'
|
def find_from_stylesheets_numbers(target_uri)
|
||||||
)
|
wp_versions = WpVersion.all
|
||||||
end
|
found = {}
|
||||||
|
pattern = /\bver=([0-9\.]+)/i
|
||||||
# Attempts to find the WordPress version from the p-links-opml.php file.
|
|
||||||
#
|
Nokogiri::HTML(Browser.get(target_uri.to_s).body).css('link,script').each do |tag|
|
||||||
# @param [ URI ] target_uri
|
%w(href src).each do |attribute|
|
||||||
#
|
attr_value = tag.attribute(attribute).to_s
|
||||||
# @return [ String ] The version number
|
|
||||||
def find_from_links_opml(target_uri)
|
next if attr_value.nil? || attr_value.empty?
|
||||||
scan_url(
|
|
||||||
target_uri,
|
begin
|
||||||
%r{generator="wordpress/#{version_pattern}"}i,
|
uri = Addressable::URI.parse(attr_value)
|
||||||
'wp-links-opml.php'
|
rescue Addressable::URI::InvalidURIError
|
||||||
)
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
next unless uri.query && uri.query.match(pattern)
|
||||||
end
|
|
||||||
|
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
|
module Output
|
||||||
|
|
||||||
def output(verbose = false)
|
def output(verbose = false)
|
||||||
|
metadata = self.metadata(self.number)
|
||||||
|
|
||||||
puts
|
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} #{"(Released on #{metadata[:release_date]}) identified from #{self.found_from}" if metadata[:release_date]}")
|
||||||
|
end
|
||||||
|
|
||||||
vulnerabilities = self.vulnerabilities
|
vulnerabilities = self.vulnerabilities
|
||||||
|
|
||||||
unless vulnerabilities.empty?
|
unless vulnerabilities.empty?
|
||||||
puts "#{critical('[!]')} #{vulnerabilities.size} vulnerabilities identified from the version number"
|
if vulnerabilities.size == 1
|
||||||
|
puts critical("#{vulnerabilities.size} vulnerability identified from the version number")
|
||||||
|
else
|
||||||
|
puts critical("#{vulnerabilities.size} vulnerabilities identified from the version number")
|
||||||
|
end
|
||||||
vulnerabilities.output
|
vulnerabilities.output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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 ]
|
# @return [ Boolean ]
|
||||||
def self.lesser_or_equal?(version1, version2)
|
def self.lesser_or_equal?(version1, version2)
|
||||||
# Prepend a '0' if the version starts with a '.'
|
# Prepend a '0' if the version starts with a '.'
|
||||||
version1 = "0#{version1}" if version1 && version1[0,1] == '.'
|
version1 = prepend_zero(version1)
|
||||||
version2 = "0#{version2}" if version2 && version2[0,1] == '.'
|
version2 = prepend_zero(version2)
|
||||||
|
|
||||||
return true if (version1 == version2)
|
return true if (version1 == version2)
|
||||||
# Both versions must be set
|
# Both versions must be set
|
||||||
@@ -27,4 +27,36 @@ class VersionCompare
|
|||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Compares two version strings. Returns true if version1 < version2
|
||||||
|
# and false otherwise
|
||||||
|
#
|
||||||
|
# @param [ String ] version1
|
||||||
|
# @param [ String ] version2
|
||||||
|
#
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def self.lesser?(version1, version2)
|
||||||
|
# Prepend a '0' if the version starts with a '.'
|
||||||
|
version1 = prepend_zero(version1)
|
||||||
|
version2 = prepend_zero(version2)
|
||||||
|
|
||||||
|
return false if (version1 == version2)
|
||||||
|
# Both versions must be set
|
||||||
|
return false unless (version1 and version2)
|
||||||
|
return false if (version1.empty? or version2.empty?)
|
||||||
|
begin
|
||||||
|
return true if (Gem::Version.new(version1) < Gem::Version.new(version2))
|
||||||
|
rescue ArgumentError => e
|
||||||
|
# Example: ArgumentError: Malformed version number string a
|
||||||
|
return false if e.message =~ /Malformed version number string/
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def self.prepend_zero(version)
|
||||||
|
return nil if version.nil?
|
||||||
|
version[0,1] == '.' ? "0#{version}" : version
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
|
|
||||||
version = RUBY_VERSION.dup
|
version = RUBY_VERSION.dup
|
||||||
if Gem::Version.create(version) < Gem::Version.create(1.9)
|
|
||||||
puts "Ruby >= 1.9 required to run wpscan (You have #{version})"
|
if Gem::Version.create(version) < Gem::Version.create(MIN_RUBY_VERSION)
|
||||||
|
puts "Ruby >= #{MIN_RUBY_VERSION} required to run wpscan (You have #{version})"
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -29,9 +30,10 @@ begin
|
|||||||
require 'shellwords'
|
require 'shellwords'
|
||||||
require 'fileutils'
|
require 'fileutils'
|
||||||
require 'pathname'
|
require 'pathname'
|
||||||
|
require 'cgi'
|
||||||
# Third party libs
|
# Third party libs
|
||||||
require 'typhoeus'
|
require 'typhoeus'
|
||||||
require 'json'
|
require 'yajl/json_gem'
|
||||||
require 'nokogiri'
|
require 'nokogiri'
|
||||||
require 'terminal-table'
|
require 'terminal-table'
|
||||||
require 'ruby-progressbar'
|
require 'ruby-progressbar'
|
||||||
|
|||||||
@@ -21,6 +21,29 @@ class WebSite
|
|||||||
@uri.to_s
|
@uri.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Checks if the remote website has ssl errors
|
||||||
|
def ssl_error?
|
||||||
|
return false unless @uri.scheme == 'https'
|
||||||
|
c = get_root_path_return_code
|
||||||
|
# http://www.rubydoc.info/github/typhoeus/ethon/Ethon/Easy:return_code
|
||||||
|
return (
|
||||||
|
c == :ssl_connect_error ||
|
||||||
|
c == :peer_failed_verification ||
|
||||||
|
c == :ssl_certproblem ||
|
||||||
|
c == :ssl_cipher ||
|
||||||
|
c == :ssl_cacert ||
|
||||||
|
c == :ssl_cacert_badfile ||
|
||||||
|
c == :ssl_issuer_error ||
|
||||||
|
c == :ssl_crl_badfile ||
|
||||||
|
c == :ssl_engine_setfailed ||
|
||||||
|
c == :ssl_engine_notfound
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_root_path_return_code
|
||||||
|
Browser.get(@uri.to_s).return_code
|
||||||
|
end
|
||||||
|
|
||||||
# Checks if the remote website is up.
|
# Checks if the remote website is up.
|
||||||
def online?
|
def online?
|
||||||
Browser.get(@uri.to_s).code != 0
|
Browser.get(@uri.to_s).code != 0
|
||||||
@@ -54,10 +77,7 @@ class WebSite
|
|||||||
|
|
||||||
redirected_uri = URI.parse(add_trailing_slash(add_http_protocol(url)))
|
redirected_uri = URI.parse(add_trailing_slash(add_http_protocol(url)))
|
||||||
if response.code == 301 || response.code == 302
|
if response.code == 301 || response.code == 302
|
||||||
redirection = response.headers_hash['location']
|
redirection = redirected_uri.merge(response.headers_hash['location']).to_s
|
||||||
if redirection[0] == '/'
|
|
||||||
redirection = "#{redirected_uri.scheme}://#{redirected_uri.host}#{redirection}"
|
|
||||||
end
|
|
||||||
|
|
||||||
return redirection if url == redirection # prevents infinite loop
|
return redirection if url == redirection # prevents infinite loop
|
||||||
|
|
||||||
@@ -71,15 +91,18 @@ class WebSite
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Compute the MD5 of the page
|
# Compute the MD5 of the page
|
||||||
# Comments are deleted from the page to avoid cache generation details
|
# Comments and scripts are deleted from the page to avoid cache generation details
|
||||||
#
|
#
|
||||||
# @param [ String, Typhoeus::Response ] page The url of the response of the page
|
# @param [ String, Typhoeus::Response ] page The url of the response of the page
|
||||||
#
|
#
|
||||||
# @return [ String ] The MD5 hash of the page
|
# @return [ String ] The MD5 hash of the page
|
||||||
def self.page_hash(page)
|
def self.page_hash(page)
|
||||||
page = Browser.get(page, { followlocation: true, cache_ttl: 0 }) unless page.is_a?(Typhoeus::Response)
|
page = Browser.get(page, { followlocation: true, cache_ttl: 0 }) unless page.is_a?(Typhoeus::Response)
|
||||||
|
# remove comments
|
||||||
Digest::MD5.hexdigest(page.body.gsub(/<!--.*?-->/m, ''))
|
page = page.body.gsub(/<!--.*?-->/m, '')
|
||||||
|
# remove javascript stuff
|
||||||
|
page = page.gsub(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/m, '')
|
||||||
|
Digest::MD5.hexdigest(page)
|
||||||
end
|
end
|
||||||
|
|
||||||
def homepage_hash
|
def homepage_hash
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class WebSite
|
|||||||
@uri.clone.merge('robots.txt').to_s
|
@uri.clone.merge('robots.txt').to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Parse robots.txt
|
# Parse robots.txt
|
||||||
# @return [ Array ] URLs generated from robots.txt
|
# @return [ Array ] URLs generated from robots.txt
|
||||||
def parse_robots_txt
|
def parse_robots_txt
|
||||||
@@ -29,6 +28,7 @@ class WebSite
|
|||||||
if entries
|
if entries
|
||||||
entries.flatten!
|
entries.flatten!
|
||||||
entries.compact.sort!
|
entries.compact.sort!
|
||||||
|
entries.uniq!
|
||||||
wordpress_path = @uri.path
|
wordpress_path = @uri.path
|
||||||
RobotsTxt.known_dirs.each do |d|
|
RobotsTxt.known_dirs.each do |d|
|
||||||
entries.delete(d)
|
entries.delete(d)
|
||||||
@@ -40,9 +40,9 @@ class WebSite
|
|||||||
entries.each do |d|
|
entries.each do |d|
|
||||||
begin
|
begin
|
||||||
temp = @uri.clone
|
temp = @uri.clone
|
||||||
temp.path = d
|
temp.path = d.strip
|
||||||
rescue URI::Error
|
rescue URI::Error
|
||||||
temp = d
|
temp = d.strip
|
||||||
end
|
end
|
||||||
return_object << temp.to_s
|
return_object << temp.to_s
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -28,8 +28,13 @@ class WpTarget < WebSite
|
|||||||
@wp_content_dir = options[:wp_content_dir]
|
@wp_content_dir = options[:wp_content_dir]
|
||||||
@wp_plugins_dir = options[:wp_plugins_dir]
|
@wp_plugins_dir = options[:wp_plugins_dir]
|
||||||
@multisite = nil
|
@multisite = nil
|
||||||
|
@vhost = options[:vhost]
|
||||||
|
|
||||||
Browser.instance.referer = url
|
Browser.instance.referer = url
|
||||||
|
if @vhost
|
||||||
|
Browser.instance.vhost = @vhost
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# check if the target website is
|
# check if the target website is
|
||||||
@@ -44,11 +49,7 @@ class WpTarget < WebSite
|
|||||||
fail "The target is responding with a 403, this might be due to a WAF or a plugin.\n" \
|
fail "The target is responding with a 403, this might be due to a WAF or a plugin.\n" \
|
||||||
'You should try to supply a valid user-agent via the --user-agent option or use the --random-agent option' if response.code == 403
|
'You should try to supply a valid user-agent via the --user-agent option or use the --random-agent option' if response.code == 403
|
||||||
|
|
||||||
if wp_content_dir
|
dir = wp_content_dir ? wp_content_dir : 'wp-content'
|
||||||
dir = wp_content_dir
|
|
||||||
else
|
|
||||||
dir = 'wp-content'
|
|
||||||
end
|
|
||||||
|
|
||||||
if response.body =~ /["'][^"']*\/#{Regexp.escape(dir)}\/[^"']*["']/i
|
if response.body =~ /["'][^"']*\/#{Regexp.escape(dir)}\/[^"']*["']/i
|
||||||
wordpress = true
|
wordpress = true
|
||||||
@@ -134,6 +135,11 @@ class WpTarget < WebSite
|
|||||||
@uri.merge("#{wp_content_dir}/uploads/").to_s
|
@uri.merge("#{wp_content_dir}/uploads/").to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def includes_dir_url
|
||||||
|
@uri.merge("wp-includes/").to_s
|
||||||
|
end
|
||||||
|
|
||||||
# Script for replacing strings in wordpress databases
|
# Script for replacing strings in wordpress databases
|
||||||
# reveals database credentials after hitting submit
|
# reveals database credentials after hitting submit
|
||||||
# http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
|
# http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
|
||||||
@@ -152,4 +158,8 @@ class WpTarget < WebSite
|
|||||||
def upload_directory_listing_enabled?
|
def upload_directory_listing_enabled?
|
||||||
directory_listing_enabled?(upload_dir_url)
|
directory_listing_enabled?(upload_dir_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def include_directory_listing_enabled?
|
||||||
|
directory_listing_enabled?(includes_dir_url)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class WpTarget < WebSite
|
|||||||
queue_count = 0
|
queue_count = 0
|
||||||
|
|
||||||
backups.each do |file|
|
backups.each do |file|
|
||||||
file_url = @uri.merge(URI.escape(file)).to_s
|
file_url = @uri.merge(url_encode(file)).to_s
|
||||||
request = browser.forge_request(file_url)
|
request = browser.forge_request(file_url)
|
||||||
|
|
||||||
request.on_complete do |response|
|
request.on_complete do |response|
|
||||||
@@ -40,7 +40,7 @@ class WpTarget < WebSite
|
|||||||
# @return [ Array ]
|
# @return [ Array ]
|
||||||
def self.config_backup_files
|
def self.config_backup_files
|
||||||
%w{
|
%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_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
|
wp-config.php.orig wp-config.orig wp-config.php.original wp-config.original wp-config.txt
|
||||||
} # thanks to Feross.org for these
|
} # thanks to Feross.org for these
|
||||||
|
|||||||
@@ -2,19 +2,21 @@
|
|||||||
|
|
||||||
class WpTarget < WebSite
|
class WpTarget < WebSite
|
||||||
module WpFullPathDisclosure
|
module WpFullPathDisclosure
|
||||||
|
|
||||||
# Check for Full Path Disclosure (FPD)
|
# Check for Full Path Disclosure (FPD)
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def has_full_path_disclosure?
|
def has_full_path_disclosure?
|
||||||
response = Browser.get(full_path_disclosure_url())
|
Browser.get(full_path_disclosure_url).body[%r/Fatal error/i] ? true : false
|
||||||
response.body[%r{Fatal error}i] ? true : false
|
end
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def full_path_disclosure_url
|
def full_path_disclosure_url
|
||||||
@uri.merge('wp-includes/rss-functions.php').to_s
|
@uri.merge('wp-includes/rss-functions.php').to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class WpTarget < WebSite
|
|||||||
@login_protection_plugin = nil
|
@login_protection_plugin = nil
|
||||||
|
|
||||||
def has_login_protection?
|
def has_login_protection?
|
||||||
!login_protection_plugin().nil?
|
!login_protection_plugin.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks if a login protection plugin is enabled
|
# Checks if a login protection plugin is enabled
|
||||||
@@ -74,7 +74,7 @@ class WpTarget < WebSite
|
|||||||
|
|
||||||
# http://wordpress.org/extend/plugins/login-security-solution/
|
# http://wordpress.org/extend/plugins/login-security-solution/
|
||||||
def has_login_security_solution_protection?
|
def has_login_security_solution_protection?
|
||||||
Browser.get(login_security_solution_url()).code != 404
|
Browser.get(login_security_solution_url).code != 404
|
||||||
end
|
end
|
||||||
|
|
||||||
def login_security_solution_url
|
def login_security_solution_url
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
class WpTarget < WebSite
|
class WpTarget < WebSite
|
||||||
module WpMustUsePlugins
|
module WpMustUsePlugins
|
||||||
|
|
||||||
# Checks to see if the must use plugin folder exists
|
# Checks to see if the must use plugin folder exists
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def has_must_use_plugins?
|
def has_must_use_plugins?
|
||||||
response = Browser.get(must_use_url)
|
response = Browser.get(must_use_url)
|
||||||
|
|
||||||
if response && WpTarget.valid_response_codes.include?(response.code)
|
if response && [200, 401, 403].include?(response.code)
|
||||||
hash = WebSite.page_hash(response)
|
hash = WebSite.page_hash(response)
|
||||||
return true if hash != error_404_hash && hash != homepage_hash
|
return true if hash != error_404_hash && hash != homepage_hash
|
||||||
end
|
end
|
||||||
@@ -21,6 +20,5 @@ class WpTarget < WebSite
|
|||||||
def must_use_url
|
def must_use_url
|
||||||
@uri.merge("#{wp_content_dir}/mu-plugins/").to_s
|
@uri.merge("#{wp_content_dir}/mu-plugins/").to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class WpTarget < WebSite
|
|||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def has_readme?
|
def has_readme?
|
||||||
response = Browser.get(readme_url())
|
response = Browser.get(readme_url)
|
||||||
|
|
||||||
unless response.code == 404
|
unless response.code == 404
|
||||||
return response.body =~ %r{wordpress}i ? true : false
|
return response.body =~ %r{wordpress}i ? true : false
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require File.expand_path(File.dirname(__FILE__) + '/../common/common_helper')
|
require File.expand_path(File.join(__dir__, '..', 'common', 'common_helper'))
|
||||||
|
|
||||||
require_files_from_directory(WPSCAN_LIB_DIR, '**/*.rb')
|
require_files_from_directory(WPSCAN_LIB_DIR, '**/*.rb')
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ def help
|
|||||||
puts
|
puts
|
||||||
puts 'Some values are settable in a config file, see the example.conf.json'
|
puts 'Some values are settable in a config file, see the example.conf.json'
|
||||||
puts
|
puts
|
||||||
puts '--update Update to the database to the latest version.'
|
puts '--update Update the database to the latest version.'
|
||||||
puts '--url | -u <target url> The WordPress URL/domain to scan.'
|
puts '--url | -u <target url> The WordPress URL/domain to scan.'
|
||||||
puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
|
puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
|
||||||
puts '--enumerate | -e [option(s)] Enumeration.'
|
puts '--enumerate | -e [option(s)] Enumeration.'
|
||||||
@@ -84,12 +84,17 @@ def help
|
|||||||
puts ' You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).'
|
puts ' You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).'
|
||||||
puts '--config-file | -c <config file> Use the specified config file, see the example.conf.json.'
|
puts '--config-file | -c <config file> Use the specified config file, see the example.conf.json.'
|
||||||
puts '--user-agent | -a <User-Agent> Use the specified User-Agent.'
|
puts '--user-agent | -a <User-Agent> Use the specified User-Agent.'
|
||||||
puts '--cookie <String> String to read cookies from.'
|
puts '--cookie <string> String to read cookies from.'
|
||||||
puts '--random-agent | -r Use a random User-Agent.'
|
puts '--random-agent | -r Use a random User-Agent.'
|
||||||
puts '--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not'
|
puts '--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not'
|
||||||
puts '--batch Never ask for user input, use the default behaviour.'
|
puts '--batch Never ask for user input, use the default behaviour.'
|
||||||
puts '--no-color Do not use colors in the output.'
|
puts '--no-color Do not use colors in the output.'
|
||||||
puts '--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.'
|
puts '--log Creates a log.txt file with WPScan\'s output.'
|
||||||
|
puts '--no-banner Prevents the WPScan banner from being displayed.'
|
||||||
|
puts '--disable-accept-header Prevents WPScan sending the Accept HTTP header.'
|
||||||
|
puts '--disable-referer Prevents setting the Referer header.'
|
||||||
|
puts '--disable-tls-checks Disables SSL/TLS certificate verification.'
|
||||||
|
puts '--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specify it.'
|
||||||
puts ' Subdirectories are allowed.'
|
puts ' Subdirectories are allowed.'
|
||||||
puts '--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory.'
|
puts '--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory.'
|
||||||
puts ' If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed'
|
puts ' If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed'
|
||||||
@@ -100,11 +105,13 @@ def help
|
|||||||
puts '--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.'
|
puts '--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.'
|
||||||
puts '--username | -U <username> Only brute force the supplied username.'
|
puts '--username | -U <username> Only brute force the supplied username.'
|
||||||
puts '--usernames <path-to-file> Only brute force the usernames from the file.'
|
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-dir <cache-directory> Set the cache directory.'
|
||||||
puts '--cache-ttl <cache-ttl> Typhoeus cache TTL.'
|
puts '--cache-ttl <cache-ttl> Typhoeus cache TTL.'
|
||||||
puts '--request-timeout <request-timeout> Request Timeout.'
|
puts '--request-timeout <request-timeout> Request Timeout.'
|
||||||
puts '--connect-timeout <connect-timeout> Connect Timeout.'
|
puts '--connect-timeout <connect-timeout> Connect Timeout.'
|
||||||
|
puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests.'
|
||||||
puts '--max-threads <max-threads> Maximum Threads.'
|
puts '--max-threads <max-threads> Maximum Threads.'
|
||||||
|
puts '--throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.'
|
||||||
puts '--help | -h This help screen.'
|
puts '--help | -h This help screen.'
|
||||||
puts '--verbose | -v Verbose output.'
|
puts '--verbose | -v Verbose output.'
|
||||||
puts '--version Output the current version and exit.'
|
puts '--version Output the current version and exit.'
|
||||||
@@ -113,13 +120,19 @@ end
|
|||||||
|
|
||||||
# Hook to check if the target if down during the scan
|
# Hook to check if the target if down during the scan
|
||||||
# And have the number of requests performed to display at the end of the scan
|
# And have the number of requests performed to display at the end of the scan
|
||||||
# The target is considered down after 10 requests with status = 0
|
# The target is considered down after 30 requests with status = 0
|
||||||
down = 0
|
down = 0
|
||||||
@total_requests_done = 0
|
@total_requests_done = 0
|
||||||
|
|
||||||
Typhoeus.on_complete do |response|
|
Typhoeus.on_complete do |response|
|
||||||
|
next if response.cached?
|
||||||
|
|
||||||
down += 1 if response.code == 0
|
down += 1 if response.code == 0
|
||||||
@total_requests_done += 1
|
@total_requests_done += 1
|
||||||
|
|
||||||
fail 'The target seems to be down' if down >= 10
|
fail 'The target seems to be down' if down >= 30
|
||||||
|
|
||||||
|
next unless Browser.instance.throttle > 0
|
||||||
|
|
||||||
|
sleep(Browser.instance.throttle)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
class WpscanOptions
|
class WpscanOptions
|
||||||
|
|
||||||
ACCESSOR_OPTIONS = [
|
ACCESSOR_OPTIONS = [
|
||||||
:batch,
|
:batch,
|
||||||
:enumerate_plugins,
|
:enumerate_plugins,
|
||||||
@@ -19,6 +18,7 @@ class WpscanOptions
|
|||||||
:proxy_auth,
|
:proxy_auth,
|
||||||
:threads,
|
:threads,
|
||||||
:url,
|
:url,
|
||||||
|
:vhost,
|
||||||
:wordlist,
|
:wordlist,
|
||||||
:force,
|
:force,
|
||||||
:update,
|
:update,
|
||||||
@@ -41,7 +41,13 @@ class WpscanOptions
|
|||||||
:cache_ttl,
|
:cache_ttl,
|
||||||
:request_timeout,
|
:request_timeout,
|
||||||
:connect_timeout,
|
:connect_timeout,
|
||||||
:max_threads
|
:max_threads,
|
||||||
|
:no_banner,
|
||||||
|
:throttle,
|
||||||
|
:disable_accept_header,
|
||||||
|
:disable_referer,
|
||||||
|
:cache_dir,
|
||||||
|
:disable_tls_checks
|
||||||
]
|
]
|
||||||
|
|
||||||
attr_accessor *ACCESSOR_OPTIONS
|
attr_accessor *ACCESSOR_OPTIONS
|
||||||
@@ -60,6 +66,10 @@ class WpscanOptions
|
|||||||
@url = URI.parse(add_http_protocol(url)).to_s
|
@url = URI.parse(add_http_protocol(url)).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def vhost=(vhost)
|
||||||
|
@vhost = vhost
|
||||||
|
end
|
||||||
|
|
||||||
def threads=(threads)
|
def threads=(threads)
|
||||||
@threads = threads.is_a?(Integer) ? threads : threads.to_i
|
@threads = threads.is_a?(Integer) ? threads : threads.to_i
|
||||||
end
|
end
|
||||||
@@ -202,7 +212,9 @@ class WpscanOptions
|
|||||||
|
|
||||||
enumerate_options_from_string(cli_value)
|
enumerate_options_from_string(cli_value)
|
||||||
else
|
else
|
||||||
raise "Unknow option : #{cli_option} with value #{cli_value}"
|
text = "Unknown option : #{cli_option}"
|
||||||
|
text << " with value #{cli_value}" if (cli_value && !cli_value.empty?)
|
||||||
|
raise text
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -245,6 +257,7 @@ class WpscanOptions
|
|||||||
def self.get_opt_long
|
def self.get_opt_long
|
||||||
GetoptLong.new(
|
GetoptLong.new(
|
||||||
['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
|
['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
|
||||||
|
['--vhost',GetoptLong::OPTIONAL_ARGUMENT],
|
||||||
['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT],
|
['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT],
|
||||||
['--username', '-U', GetoptLong::REQUIRED_ARGUMENT],
|
['--username', '-U', GetoptLong::REQUIRED_ARGUMENT],
|
||||||
['--usernames', GetoptLong::REQUIRED_ARGUMENT],
|
['--usernames', GetoptLong::REQUIRED_ARGUMENT],
|
||||||
@@ -273,7 +286,13 @@ class WpscanOptions
|
|||||||
['--batch', GetoptLong::NO_ARGUMENT],
|
['--batch', GetoptLong::NO_ARGUMENT],
|
||||||
['--no-color', GetoptLong::NO_ARGUMENT],
|
['--no-color', GetoptLong::NO_ARGUMENT],
|
||||||
['--cookie', GetoptLong::REQUIRED_ARGUMENT],
|
['--cookie', GetoptLong::REQUIRED_ARGUMENT],
|
||||||
['--log', GetoptLong::NO_ARGUMENT]
|
['--log', GetoptLong::NO_ARGUMENT],
|
||||||
|
['--no-banner', GetoptLong::NO_ARGUMENT],
|
||||||
|
['--throttle', GetoptLong::REQUIRED_ARGUMENT],
|
||||||
|
['--disable-accept-header', GetoptLong::NO_ARGUMENT],
|
||||||
|
['--disable-referer', GetoptLong::NO_ARGUMENT],
|
||||||
|
['--cache-dir', GetoptLong::REQUIRED_ARGUMENT],
|
||||||
|
['--disable-tls-checks', GetoptLong::NO_ARGUMENT],
|
||||||
)
|
)
|
||||||
end
|
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
|
it 'raises an error' do
|
||||||
File.symlink('./testfile', config_file)
|
File.symlink('./testfile', config_file)
|
||||||
expect { browser.load_config(config_file) }.to raise_error("[ERROR] Config file is a symlink.")
|
expect { browser.load_config(config_file) }.to raise_error('[ERROR] Config file is a symlink.')
|
||||||
File.unlink(config_file)
|
File.unlink(config_file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -124,29 +124,37 @@ describe Browser do
|
|||||||
describe '#merge_request_params' do
|
describe '#merge_request_params' do
|
||||||
let(:params) { {} }
|
let(:params) { {} }
|
||||||
let(:cookie_jar) { CACHE_DIR + '/browser/cookie-jar' }
|
let(:cookie_jar) { CACHE_DIR + '/browser/cookie-jar' }
|
||||||
|
let(:user_agent) { 'SomeUA' }
|
||||||
let(:default_expectation) {
|
let(:default_expectation) {
|
||||||
{
|
{
|
||||||
cache_ttl: 250,
|
cache_ttl: 250,
|
||||||
headers: { 'User-Agent' => 'SomeUA' },
|
|
||||||
ssl_verifypeer: false, ssl_verifyhost: 0,
|
|
||||||
cookiejar: cookie_jar, cookiefile: cookie_jar,
|
cookiejar: cookie_jar, cookiefile: cookie_jar,
|
||||||
timeout: 2000, connecttimeout: 1000,
|
timeout: 60, connecttimeout: 10,
|
||||||
maxredirs: 3,
|
maxredirs: 3,
|
||||||
referer: nil
|
referer: nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
after :each do
|
after :each do
|
||||||
browser.user_agent = 'SomeUA'
|
browser.user_agent = user_agent
|
||||||
browser.cache_ttl = 250
|
browser.cache_ttl = 250
|
||||||
|
|
||||||
expect(browser.merge_request_params(params)).to eq @expected
|
expect(browser.merge_request_params(params)).to eq @expected
|
||||||
|
expect(Typhoeus::Config.user_agent).to eq user_agent
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets the User-Agent header field and cache_ttl' do
|
it 'sets the User-Agent header field and cache_ttl' do
|
||||||
@expected = default_expectation
|
@expected = default_expectation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when @user_agent' do
|
||||||
|
let(:user_agent) { 'test' }
|
||||||
|
|
||||||
|
it 'sets the User-Agent' do
|
||||||
|
@expected = default_expectation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when @proxy' do
|
context 'when @proxy' do
|
||||||
let(:proxy) { '127.0.0.1:9050' }
|
let(:proxy) { '127.0.0.1:9050' }
|
||||||
let(:proxy_expectation) { default_expectation.merge(proxy: proxy) }
|
let(:proxy_expectation) { default_expectation.merge(proxy: proxy) }
|
||||||
@@ -177,7 +185,7 @@ describe Browser do
|
|||||||
it 'appends the basic_auth' do
|
it 'appends the basic_auth' do
|
||||||
browser.basic_auth = 'user:pass'
|
browser.basic_auth = 'user:pass'
|
||||||
@expected = default_expectation.merge(
|
@expected = default_expectation.merge(
|
||||||
headers: default_expectation[:headers].merge('Authorization' => 'Basic ' + Base64.encode64('user:pass').chomp)
|
headers: { 'Authorization' => 'Basic ' + Base64.encode64('user:pass').chomp }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -206,6 +214,13 @@ describe Browser do
|
|||||||
@expected = default_expectation.merge(cookie: cookie)
|
@expected = default_expectation.merge(cookie: cookie)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when @disable_tls_checks' do
|
||||||
|
it 'disables tls checks' do
|
||||||
|
browser.disable_tls_checks = true
|
||||||
|
@expected = default_expectation.merge(ssl_verifypeer: 0, ssl_verifyhost: 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#forge_request' do
|
describe '#forge_request' do
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ describe CacheFileStore do
|
|||||||
it 'should create a unique storage dir' do
|
it 'should create a unique storage dir' do
|
||||||
storage_dirs = []
|
storage_dirs = []
|
||||||
|
|
||||||
(1..5).each do |i|
|
(1..5).each do |_|
|
||||||
storage_dirs << CacheFileStore.new(cache_dir).storage_path
|
storage_dirs << CacheFileStore.new(cache_dir).storage_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ describe WpItems do
|
|||||||
vulnerable_targets_items: [ WpItem.new(uri, name: 'mr-smith'),
|
vulnerable_targets_items: [ WpItem.new(uri, name: 'mr-smith'),
|
||||||
WpItem.new(uri, name: 'neo')],
|
WpItem.new(uri, name: 'neo')],
|
||||||
|
|
||||||
passive_detection: (1..13).reduce(WpItems.new) { |o, i| o << WpItem.new(uri, name: "detect-me-#{i}") }
|
passive_detection: (1..15).reduce(WpItems.new) { |o, i| o << WpItem.new(uri, name: "detect-me-#{i}") }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -100,6 +100,13 @@ describe 'WpPlugins::Detectable' do
|
|||||||
expected.add('all-in-one-seo-pack', version: '2.0.3.1')
|
expected.add('all-in-one-seo-pack', version: '2.0.3.1')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when google-universal-analytics detected' do
|
||||||
|
it 'returns google-universal-analytics' do
|
||||||
|
@body = '<!-- Google Universal Analytics for WordPress v2.4.2 -->'
|
||||||
|
expected.add('google-universal-analytics', version: '2.4.2')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe WpPlugins do
|
|||||||
let(:expected) do
|
let(:expected) do
|
||||||
{
|
{
|
||||||
request_params: { cache_ttl: 0, followlocation: true },
|
request_params: { cache_ttl: 0, followlocation: true },
|
||||||
vulns_file: PLUGINS_VULNS_FILE,
|
vulns_file: PLUGINS_FILE,
|
||||||
targets_items_from_file: [ WpPlugin.new(uri, name: 'plugin1'),
|
targets_items_from_file: [ WpPlugin.new(uri, name: 'plugin1'),
|
||||||
WpPlugin.new(uri, name:'plugin-2'),
|
WpPlugin.new(uri, name:'plugin-2'),
|
||||||
WpPlugin.new(uri, name: 'mr-smith')],
|
WpPlugin.new(uri, name: 'mr-smith')],
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe WpThemes do
|
|||||||
let(:expected) do
|
let(:expected) do
|
||||||
{
|
{
|
||||||
request_params: { cache_ttl: 0, followlocation: true },
|
request_params: { cache_ttl: 0, followlocation: true },
|
||||||
vulns_file: THEMES_VULNS_FILE,
|
vulns_file: THEMES_FILE,
|
||||||
targets_items_from_file: [ WpTheme.new(uri, name: '3colours'),
|
targets_items_from_file: [ WpTheme.new(uri, name: '3colours'),
|
||||||
WpTheme.new(uri, name:'42k'),
|
WpTheme.new(uri, name:'42k'),
|
||||||
WpTheme.new(uri, name: 'a-ri')],
|
WpTheme.new(uri, name: 'a-ri')],
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ describe 'WpTimthumbs::Detectable' do
|
|||||||
|
|
||||||
def expected_targets_from_theme(theme_name)
|
def expected_targets_from_theme(theme_name)
|
||||||
expected = []
|
expected = []
|
||||||
%w{
|
%w(
|
||||||
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
||||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
|
scripts/timthumb.php tools/timthumb.php functions/timthumb.php thumb.php
|
||||||
}.each do |file|
|
).each do |file|
|
||||||
path = "$wp-content$/themes/#{theme_name}/#{file}"
|
path = "$wp-content$/themes/#{theme_name}/#{file}"
|
||||||
expected << WpTimthumb.new(uri, path: path)
|
expected << WpTimthumb.new(uri, path: path)
|
||||||
end
|
end
|
||||||
@@ -46,7 +46,7 @@ describe 'WpTimthumbs::Detectable' do
|
|||||||
after do
|
after do
|
||||||
targets = subject.send(:targets_items_from_file, file, wp_target)
|
targets = subject.send(:targets_items_from_file, file, wp_target)
|
||||||
|
|
||||||
expect(targets.map { |t| t.url }).to eq @expected.map { |t| t.url }
|
expect(targets.map(&:url)).to eq @expected.map(&:url)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when an empty file' do
|
context 'when an empty file' do
|
||||||
@@ -71,7 +71,7 @@ describe 'WpTimthumbs::Detectable' do
|
|||||||
theme = 'hello-world'
|
theme = 'hello-world'
|
||||||
targets = subject.send(:theme_timthumbs, theme, wp_target)
|
targets = subject.send(:theme_timthumbs, theme, wp_target)
|
||||||
|
|
||||||
expect(targets.map { |t| t.url }).to eq expected_targets_from_theme(theme).map { |t| t.url }
|
expect(targets.map(&:url)).to eq expected_targets_from_theme(theme).map(&:url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ describe 'WpTimthumbs::Detectable' do
|
|||||||
after do
|
after do
|
||||||
targets = subject.send(:targets_items, wp_target, options)
|
targets = subject.send(:targets_items, wp_target, options)
|
||||||
|
|
||||||
expect(targets.map { |t| t.url }).to eq @expected.sort.map { |t| t.url }
|
expect(targets.map(&:url)).to match_array(@expected.map(&:url))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no :theme_name' do
|
context 'when no :theme_name' do
|
||||||
@@ -101,7 +101,7 @@ describe 'WpTimthumbs::Detectable' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when :theme_name' do
|
context 'when :theme_name' do
|
||||||
let(:theme) { 'theme-name'}
|
let(:theme) { 'theme-name' }
|
||||||
|
|
||||||
context 'when no :file' do
|
context 'when no :file' do
|
||||||
let(:options) { { theme_name: theme } }
|
let(:options) { { theme_name: theme } }
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ describe WpItem do
|
|||||||
end
|
end
|
||||||
it_behaves_like 'WpItem::Versionable'
|
it_behaves_like 'WpItem::Versionable'
|
||||||
it_behaves_like 'WpItem::Vulnerable' do
|
it_behaves_like 'WpItem::Vulnerable' do
|
||||||
let(:vulns_file) { MODELS_FIXTURES + '/wp_item/vulnerable/items_vulns.json' }
|
let(:db_file) { MODELS_FIXTURES + '/wp_item/vulnerable/items_vulns.json' }
|
||||||
let(:identifier) { 'neo' }
|
let(:identifier) { 'neo' }
|
||||||
let(:expected_refs) { {
|
let(:expected_refs) { {
|
||||||
'id' => [2993],
|
'id' => [2993],
|
||||||
'url' => ['Ref 1,Ref 2'],
|
'url' => ['Ref 1', 'Ref 2'],
|
||||||
'cve' => ['2011-001'],
|
'cve' => ['2011-001'],
|
||||||
'secunia' => ['secunia'],
|
'secunia' => ['secunia'],
|
||||||
'osvdb' => ['osvdb'],
|
'osvdb' => ['osvdb'],
|
||||||
@@ -105,11 +105,6 @@ describe WpItem do
|
|||||||
@expected = 'plugins/readme.txt'
|
@expected = 'plugins/readme.txt'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'also encodes chars' do
|
|
||||||
@path = 'some dir with spaces'
|
|
||||||
@expected = 'some%20dir%20with%20spaces'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#uri' do
|
describe '#uri' do
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ require 'spec_helper'
|
|||||||
describe WpPlugin do
|
describe WpPlugin do
|
||||||
it_behaves_like 'WpPlugin::Vulnerable'
|
it_behaves_like 'WpPlugin::Vulnerable'
|
||||||
it_behaves_like 'WpItem::Vulnerable' do
|
it_behaves_like 'WpItem::Vulnerable' do
|
||||||
let(:options) { { name: 'white-rabbit' } }
|
let(:options) { { name: 'white-rabbit' } }
|
||||||
let(:vulns_file) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins_vulns.json' }
|
let(:db_file) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins.json' }
|
||||||
let(:expected_refs) { {
|
let(:expected_refs) { {
|
||||||
'id' => [2993],
|
'id' => [2993],
|
||||||
'url' => ['Ref 1,Ref 2'],
|
'url' => ['Ref 1', 'Ref 2'],
|
||||||
'cve' => ['2011-001'],
|
'cve' => ['2011-001'],
|
||||||
'secunia' => ['secunia'],
|
'secunia' => ['secunia'],
|
||||||
'osvdb' => ['osvdb'],
|
'osvdb' => ['osvdb'],
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ describe 'WpTheme::Findable' do
|
|||||||
|
|
||||||
wp_theme = WpTheme.send(:find_from_css_link, uri)
|
wp_theme = WpTheme.send(:find_from_css_link, uri)
|
||||||
|
|
||||||
if @expected
|
expect(wp_theme).to be_a WpTheme if @expected
|
||||||
expect(wp_theme).to be_a WpTheme
|
|
||||||
end
|
|
||||||
expect(wp_theme).to eq @expected
|
expect(wp_theme).to eq @expected
|
||||||
|
expect(wp_theme.wp_content_dir).to eql 'wp-content' if @expected
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when theme is not present' do
|
context 'when theme is not present' do
|
||||||
@@ -59,6 +58,13 @@ describe 'WpTheme::Findable' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# This one might introduce FP btw
|
||||||
|
context 'when leaked from comments' do
|
||||||
|
it 'returns the WpTheme' do
|
||||||
|
@file = 'comments.html'
|
||||||
|
@expected = WpTheme.new(uri, name: 'debug')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '::find_from_wooframework' do
|
describe '::find_from_wooframework' do
|
||||||
@@ -96,7 +102,6 @@ describe 'WpTheme::Findable' do
|
|||||||
@expected = WpTheme.new(uri, name: 'Editorial', version: '1.3.5')
|
@expected = WpTheme.new(uri, name: 'Editorial', version: '1.3.5')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '::find' do
|
describe '::find' do
|
||||||
@@ -109,7 +114,6 @@ describe 'WpTheme::Findable' do
|
|||||||
|
|
||||||
context 'when a method is named s_find_from_s' do
|
context 'when a method is named s_find_from_s' do
|
||||||
it 'does not call it' do
|
it 'does not call it' do
|
||||||
|
|
||||||
class WpTheme
|
class WpTheme
|
||||||
module Findable
|
module Findable
|
||||||
extend self
|
extend self
|
||||||
@@ -117,7 +121,7 @@ describe 'WpTheme::Findable' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
stub_all_to_nil()
|
stub_all_to_nil
|
||||||
|
|
||||||
expect { WpTheme.find(uri) }.to_not raise_error
|
expect { WpTheme.find(uri) }.to_not raise_error
|
||||||
end
|
end
|
||||||
@@ -125,7 +129,7 @@ describe 'WpTheme::Findable' do
|
|||||||
|
|
||||||
context 'when the theme is not found' do
|
context 'when the theme is not found' do
|
||||||
it 'returns nil' do
|
it 'returns nil' do
|
||||||
stub_all_to_nil()
|
stub_all_to_nil
|
||||||
|
|
||||||
expect(WpTheme.find(uri)).to be_nil
|
expect(WpTheme.find(uri)).to be_nil
|
||||||
end
|
end
|
||||||
@@ -133,7 +137,7 @@ describe 'WpTheme::Findable' do
|
|||||||
|
|
||||||
context 'when the theme is found' do
|
context 'when the theme is found' do
|
||||||
it 'returns it, with the :found_from set' do
|
it 'returns it, with the :found_from set' do
|
||||||
stub_all_to_nil()
|
stub_all_to_nil
|
||||||
stub_request(:get, /.+\/the-oracle\/style.css$/).to_return(status: 200)
|
stub_request(:get, /.+\/the-oracle\/style.css$/).to_return(status: 200)
|
||||||
expected = WpTheme.new(uri, name: 'the-oracle')
|
expected = WpTheme.new(uri, name: 'the-oracle')
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ describe WpTheme do
|
|||||||
it_behaves_like 'WpTheme::Vulnerable'
|
it_behaves_like 'WpTheme::Vulnerable'
|
||||||
it_behaves_like 'WpItem::Vulnerable' do
|
it_behaves_like 'WpItem::Vulnerable' do
|
||||||
let(:options) { { name: 'the-oracle' } }
|
let(:options) { { name: 'the-oracle' } }
|
||||||
let(:vulns_file) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' }
|
let(:db_file) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' }
|
||||||
let(:expected_refs) { {
|
let(:expected_refs) { {
|
||||||
'id' => [2993],
|
'id' => [2993],
|
||||||
'url' => ['Ref 1,Ref 2'],
|
'url' => ['Ref 1', 'Ref 2'],
|
||||||
'cve' => ['2011-001'],
|
'cve' => ['2011-001'],
|
||||||
'secunia' => ['secunia'],
|
'secunia' => ['secunia'],
|
||||||
'osvdb' => ['osvdb'],
|
'osvdb' => ['osvdb'],
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ describe 'WpVersion::Findable' do
|
|||||||
@fixture = '/3.5_minified.html'
|
@fixture = '/3.5_minified.html'
|
||||||
@expected = '3.5'
|
@expected = '3.5'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'returns 3.5.1' do
|
||||||
|
@fixture = '/3.5.1_mobile.html'
|
||||||
|
@expected = '3.5.1'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -154,6 +159,22 @@ describe 'WpVersion::Findable' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '::find_from_stylesheets_numbers' do
|
||||||
|
after do
|
||||||
|
fixture = fixtures_dir + 'stylesheet_numbers' + @fixture
|
||||||
|
stub_request_to_fixture(url: uri, fixture: fixture)
|
||||||
|
|
||||||
|
expect(WpVersion.send(:find_from_stylesheets_numbers, uri)).to eq @expected
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'invalid url' do
|
||||||
|
it 'returns nil' do
|
||||||
|
@fixture = '/invalid_url.html'
|
||||||
|
@expected = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '::find' do
|
describe '::find' do
|
||||||
# Stub all WpVersion::find_from_* to return nil
|
# Stub all WpVersion::find_from_* to return nil
|
||||||
def stub_all_to_nil
|
def stub_all_to_nil
|
||||||
@@ -178,7 +199,7 @@ describe 'WpVersion::Findable' do
|
|||||||
|
|
||||||
context 'when no version found' do
|
context 'when no version found' do
|
||||||
it 'returns nil' do
|
it 'returns nil' do
|
||||||
stub_all_to_nil()
|
stub_all_to_nil
|
||||||
@expected = nil
|
@expected = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -188,8 +209,8 @@ describe 'WpVersion::Findable' do
|
|||||||
found_from = method[/^find_from_(.*)/, 1].sub('_', ' ')
|
found_from = method[/^find_from_(.*)/, 1].sub('_', ' ')
|
||||||
|
|
||||||
context "when found from #{found_from}" do
|
context "when found from #{found_from}" do
|
||||||
it "returns the correct WpVersion" do
|
it 'returns the correct WpVersion' do
|
||||||
stub_all_to_nil()
|
stub_all_to_nil
|
||||||
|
|
||||||
allow(WpVersion).to receive(method).and_return(number)
|
allow(WpVersion).to receive(method).and_return(number)
|
||||||
|
|
||||||
|
|||||||
@@ -4,20 +4,6 @@ require 'spec_helper'
|
|||||||
|
|
||||||
describe WpVersion do
|
describe WpVersion do
|
||||||
it_behaves_like 'WpVersion::Vulnerable'
|
it_behaves_like 'WpVersion::Vulnerable'
|
||||||
it_behaves_like 'WpItem::Vulnerable' do
|
|
||||||
let(:options) { { number: '3.2' } }
|
|
||||||
let(:vulns_file) { MODELS_FIXTURES + '/wp_version/vulnerable/versions_vulns.json' }
|
|
||||||
let(:expected_refs) { {
|
|
||||||
'id' => [2993],
|
|
||||||
'url' => ['Ref 1,Ref 2'],
|
|
||||||
'cve' => ['2011-001'],
|
|
||||||
'secunia' => ['secunia'],
|
|
||||||
'osvdb' => ['osvdb'],
|
|
||||||
'metasploit' => ['exploit/ex1'],
|
|
||||||
'exploitdb' => ['exploitdb']
|
|
||||||
} }
|
|
||||||
let(:expected_vulns) { Vulnerabilities.new << Vulnerability.new('Here I Am', 'SQLI', expected_refs) }
|
|
||||||
end
|
|
||||||
|
|
||||||
subject(:wp_version) { WpVersion.new(uri, options) }
|
subject(:wp_version) { WpVersion.new(uri, options) }
|
||||||
let(:uri) { URI.parse('http://example.com/') }
|
let(:uri) { URI.parse('http://example.com/') }
|
||||||
|
|||||||
@@ -121,4 +121,127 @@ describe 'VersionCompare' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '::lesser?' do
|
||||||
|
context 'version checked is newer' do
|
||||||
|
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_truthy }
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
@version1 = '1.0'
|
||||||
|
@version2 = '2.0'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
@version1 = '1.0'
|
||||||
|
@version2 = '1.1'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
@version1 = '1.0a'
|
||||||
|
@version2 = '1.0b'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
@version1 = '1.0'
|
||||||
|
@version2 = '5000000'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
@version1 = '0'
|
||||||
|
@version2 = '1'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
@version1 = '0.4.2b'
|
||||||
|
@version2 = '2.3.3'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
@version1 = '.47'
|
||||||
|
@version2 = '.50.3'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
@version1 = '2.5.9'
|
||||||
|
@version2 = '2.5.10'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'version checked is older' do
|
||||||
|
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
@version1 = '1'
|
||||||
|
@version2 = '0'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
@version1 = '1.0'
|
||||||
|
@version2 = '0.5'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
@version1 = '500000'
|
||||||
|
@version2 = '1'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
@version1 = '1.6.3.7.3.4'
|
||||||
|
@version2 = '1.2.4.567.679.8.e'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
@version1 = '.47'
|
||||||
|
@version2 = '.46.3'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'version checked is the same' do
|
||||||
|
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
@version1 = '1'
|
||||||
|
@version2 = '1'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
@version1 = 'a'
|
||||||
|
@version2 = 'a'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'version number causes Gem::Version new Exception' do
|
||||||
|
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
@version1 = 'a'
|
||||||
|
@version2 = 'b'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'one version number is not set' do
|
||||||
|
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
|
||||||
|
|
||||||
|
it 'returns false (version2 nil)' do
|
||||||
|
@version1 = '1'
|
||||||
|
@version2 = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false (version1 nil)' do
|
||||||
|
@version1 = nil
|
||||||
|
@version2 = '1'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false (version2 empty)' do
|
||||||
|
@version1 = '1'
|
||||||
|
@version2 = ''
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false (version1 empty)' do
|
||||||
|
@version1 = ''
|
||||||
|
@version2 = '1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ describe 'WebSite' do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#new" do
|
describe '#new' do
|
||||||
its(:url) { is_expected.to be === 'http://example.localhost/' }
|
its(:url) { is_expected.to be === 'http://example.localhost/' }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -68,14 +68,14 @@ describe 'WebSite' do
|
|||||||
|
|
||||||
describe '#xml_rpc_url' do
|
describe '#xml_rpc_url' do
|
||||||
it 'returns the xmlrpc url' do
|
it 'returns the xmlrpc url' do
|
||||||
expect(web_site.xml_rpc_url).to be === "http://example.localhost/xmlrpc.php"
|
expect(web_site.xml_rpc_url).to be === 'http://example.localhost/xmlrpc.php'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#has_xml_rpc?' do
|
describe '#has_xml_rpc?' do
|
||||||
it 'returns true' do
|
it 'returns true' do
|
||||||
stub_request(:get, web_site.xml_rpc_url).
|
stub_request(:get, web_site.xml_rpc_url).
|
||||||
to_return(status: 200, body: "XML-RPC server accepts POST requests only")
|
to_return(status: 200, body: 'XML-RPC server accepts POST requests only')
|
||||||
|
|
||||||
expect(web_site).to have_xml_rpc
|
expect(web_site).to have_xml_rpc
|
||||||
end
|
end
|
||||||
@@ -116,12 +116,24 @@ describe 'WebSite' do
|
|||||||
|
|
||||||
expect(web_site.redirection).to eql absolute_location
|
expect(web_site.redirection).to eql absolute_location
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when starts with a ?' do
|
||||||
|
it 'returns the absolute URI' do
|
||||||
|
relative_location = '?p=blog'
|
||||||
|
absolute_location = web_site.uri.merge(relative_location).to_s
|
||||||
|
|
||||||
|
stub_request(:get, web_site.url).to_return(status: 301, headers: { location: relative_location })
|
||||||
|
stub_request(:get, absolute_location)
|
||||||
|
|
||||||
|
expect(web_site.redirection).to eql absolute_location
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when multiple redirections' do
|
context 'when multiple redirections' do
|
||||||
it 'returns the last redirection' do
|
it 'returns the last redirection' do
|
||||||
first_redirection = 'www.redirection.com'
|
first_redirection = 'http://www.redirection.com'
|
||||||
last_redirection = 'redirection.com'
|
last_redirection = 'http://redirection.com'
|
||||||
|
|
||||||
stub_request(:get, web_site.url).to_return(status: 301, headers: { location: first_redirection })
|
stub_request(:get, web_site.url).to_return(status: 301, headers: { location: first_redirection })
|
||||||
stub_request(:get, first_redirection).to_return(status: 302, headers: { location: last_redirection })
|
stub_request(:get, first_redirection).to_return(status: 302, headers: { location: last_redirection })
|
||||||
@@ -164,6 +176,17 @@ describe 'WebSite' do
|
|||||||
@expected = "yolo\n\n\nworld!"
|
@expected = "yolo\n\n\nworld!"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when there are scripts' do
|
||||||
|
let(:page) {
|
||||||
|
body = "yolo\n\n<script type=\"text/javascript\">alert('Hi');</script>\nworld!"
|
||||||
|
Typhoeus::Response.new(body: body)
|
||||||
|
}
|
||||||
|
|
||||||
|
it 'removes them' do
|
||||||
|
@expected = "yolo\n\n\nworld!"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#homepage_hash' do
|
describe '#homepage_hash' do
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
require File.expand_path(File.join(__dir__, 'wpscan_helper'))
|
||||||
|
|
||||||
describe WpTarget do
|
describe WpTarget do
|
||||||
subject(:wp_target) { WpTarget.new(target_url, options) }
|
subject(:wp_target) { WpTarget.new(target_url, options) }
|
||||||
@@ -149,7 +149,7 @@ describe WpTarget do
|
|||||||
|
|
||||||
after :each do
|
after :each do
|
||||||
allow(wp_target).to receive_messages(wp_content_dir: 'wp-content')
|
allow(wp_target).to receive_messages(wp_content_dir: 'wp-content')
|
||||||
stub_request_to_fixture(url: wp_target.debug_log_url(), fixture: @fixture)
|
stub_request_to_fixture(url: wp_target.debug_log_url, fixture: @fixture)
|
||||||
expect(wp_target.has_debug_log?).to be === @expected
|
expect(wp_target.has_debug_log?).to be === @expected
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# encoding: UTF-8
|
# encoding: UTF-8
|
||||||
|
|
||||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
require File.expand_path(File.join(__dir__, 'wpscan_helper'))
|
||||||
|
|
||||||
describe 'WpscanOptions' do
|
describe 'WpscanOptions' 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'
|
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
<script type='text/javascript' src='//wp-content/items/detect-me-5/main.js'></script>
|
<script type='text/javascript' src='//wp-content/items/detect-me-5/main.js'></script>
|
||||||
|
|
||||||
|
<link rel='stylesheet' id='ai1ec_style-css' href='//example.com/wp-content/items/detect-me-15/test.css' type='text/css' media='all' />
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
#fancybox-loading.fancybox-ie div {
|
#fancybox-loading.fancybox-ie div {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -32,6 +34,7 @@
|
|||||||
@import url('/wp-content/items/detect-me-8/css/datatables.css?ver=1.9.4');
|
@import url('/wp-content/items/detect-me-8/css/datatables.css?ver=1.9.4');
|
||||||
@import url(/wp-content/items/detect-me-9/css/datatables.css?ver=1.9.4);
|
@import url(/wp-content/items/detect-me-9/css/datatables.css?ver=1.9.4);
|
||||||
@import url(//wp-content/items/detect-me-10/css/datatables.css?ver=1.9.4);
|
@import url(//wp-content/items/detect-me-10/css/datatables.css?ver=1.9.4);
|
||||||
|
@import url(//example.com/wp-content/items/detect-me-14/css/datatables.css?ver=1.9.4);
|
||||||
/* ]]> */
|
/* ]]> */
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +1,64 @@
|
|||||||
[
|
{
|
||||||
{
|
"mr-smith": {
|
||||||
"mr-smith":{
|
"vulnerabilities":[
|
||||||
"vulnerabilities":[
|
{
|
||||||
{
|
"id":2989,
|
||||||
"id":2989,
|
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
"references": {
|
||||||
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com",
|
"url": "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"
|
|
||||||
},
|
},
|
||||||
{
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
"id":2990,
|
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||||
"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",
|
{
|
||||||
|
"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",
|
"osvdb":"105620",
|
||||||
"cve":"2014-0166",
|
"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",
|
||||||
"id":2991,
|
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||||
"title":"Privilege escalation: contributors publishing posts",
|
"fixed_in":"3.8.2"
|
||||||
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
},
|
||||||
|
{
|
||||||
|
"id":2991,
|
||||||
|
"title":"Privilege escalation: contributors publishing posts",
|
||||||
|
"references": {
|
||||||
|
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||||
"osvdb":"105630",
|
"osvdb":"105630",
|
||||||
"cve":"2014-0165",
|
"cve":"2014-0165"
|
||||||
"created_at":"2014-07-28T12:10:07.000Z",
|
|
||||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
|
||||||
"fixed_in":"3.8.2"
|
|
||||||
},
|
},
|
||||||
{
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
"id":2992,
|
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||||
"title":"Plupload Unspecified XSS",
|
"fixed_in":"3.8.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":2992,
|
||||||
|
"title":"Plupload Unspecified XSS",
|
||||||
|
"references": {
|
||||||
"osvdb":"105622",
|
"osvdb":"105622",
|
||||||
"secunia":"57769",
|
"secunia":"57769"
|
||||||
"created_at":"2014-07-28T12:10:07.000Z",
|
},
|
||||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
"fixed_in":"3.8.2"
|
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||||
}
|
"fixed_in":"3.8.2"
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
{
|
"neo": {
|
||||||
"neo":{
|
"vulnerabilities":[
|
||||||
"vulnerabilities":[
|
{
|
||||||
{
|
"id":2993,
|
||||||
"id":2993,
|
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
"references": {
|
||||||
"references":"http://seclists.org/fulldisclosure/2013/Dec/135",
|
"url": "http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||||
"osvdb":"101101",
|
"osvdb":"101101"
|
||||||
"created_at":"2014-07-28T12:10:07.000Z",
|
},
|
||||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
}
|
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
|
|||||||
@@ -1,58 +1,64 @@
|
|||||||
[
|
{
|
||||||
{
|
"mr-smith": {
|
||||||
"mr-smith":{
|
"vulnerabilities":[
|
||||||
"vulnerabilities":[
|
{
|
||||||
{
|
"id":2989,
|
||||||
"id":2989,
|
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
"references": {
|
||||||
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com",
|
"url": "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"
|
|
||||||
},
|
},
|
||||||
{
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
"id":2990,
|
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||||
"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",
|
"id":2990,
|
||||||
"cve":"2014-0166",
|
"title":"Potential Authentication Cookie Forgery",
|
||||||
"created_at":"2014-07-28T12:10:07.000Z",
|
"references": {
|
||||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be"
|
||||||
"fixed_in":"3.8.2"
|
|
||||||
},
|
},
|
||||||
{
|
"osvdb":"105620",
|
||||||
"id":2991,
|
"cve":"2014-0166",
|
||||||
"title":"Privilege escalation: contributors publishing posts",
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
"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",
|
"osvdb":"105630",
|
||||||
"cve":"2014-0165",
|
"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",
|
||||||
"id":2992,
|
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||||
"title":"Plupload Unspecified XSS",
|
"fixed_in":"3.8.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":2992,
|
||||||
|
"title":"Plupload Unspecified XSS",
|
||||||
|
"references": {
|
||||||
"osvdb":"105622",
|
"osvdb":"105622",
|
||||||
"secunia":"57769",
|
"secunia":"57769"
|
||||||
"created_at":"2014-07-28T12:10:07.000Z",
|
},
|
||||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
"fixed_in":"3.8.2"
|
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||||
}
|
"fixed_in":"3.8.2"
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
{
|
"neo": {
|
||||||
"neo":{
|
"vulnerabilities":[
|
||||||
"vulnerabilities":[
|
{
|
||||||
{
|
"id":2993,
|
||||||
"id":2993,
|
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
"references": {
|
||||||
"references":"http://seclists.org/fulldisclosure/2013/Dec/135",
|
"url": "http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||||
"osvdb":"101101",
|
"osvdb":"101101"
|
||||||
"created_at":"2014-07-28T12:10:07.000Z",
|
},
|
||||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
}
|
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
|
|||||||
@@ -1,58 +1,65 @@
|
|||||||
[
|
{
|
||||||
{
|
"shopperpress": {
|
||||||
"shopperpress":{
|
"vulnerabilities":[
|
||||||
"vulnerabilities":[
|
{
|
||||||
{
|
"id":2989,
|
||||||
"id":2989,
|
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
"references": {
|
||||||
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com",
|
"url": "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"
|
|
||||||
},
|
},
|
||||||
{
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
"id":2990,
|
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||||
"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",
|
"id":2990,
|
||||||
"cve":"2014-0166",
|
"title":"Potential Authentication Cookie Forgery",
|
||||||
"created_at":"2014-07-28T12:10:07.000Z",
|
"references": {
|
||||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
|
||||||
"fixed_in":"3.8.2"
|
"osvdb":"105620",
|
||||||
|
"cve":"2014-0166"
|
||||||
},
|
},
|
||||||
{
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
"id":2991,
|
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||||
"title":"Privilege escalation: contributors publishing posts",
|
"fixed_in":"3.8.2"
|
||||||
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
},
|
||||||
"osvdb":"105630",
|
{
|
||||||
"cve":"2014-0165",
|
"id":2991,
|
||||||
"created_at":"2014-07-28T12:10:07.000Z",
|
"title":"Privilege escalation: contributors publishing posts",
|
||||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
"references": {
|
||||||
"fixed_in":"3.8.2"
|
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||||
|
"osvdb":"105630",
|
||||||
|
"cve":"2014-0165"
|
||||||
},
|
},
|
||||||
{
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
"id":2992,
|
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||||
"title":"Plupload Unspecified XSS",
|
"fixed_in":"3.8.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":2992,
|
||||||
|
"title":"Plupload Unspecified XSS",
|
||||||
|
"references": {
|
||||||
"osvdb":"105622",
|
"osvdb":"105622",
|
||||||
"secunia":"57769",
|
"secunia":"57769"
|
||||||
"created_at":"2014-07-28T12:10:07.000Z",
|
},
|
||||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
"fixed_in":"3.8.2"
|
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||||
}
|
"fixed_in":"3.8.2"
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
{
|
"webfolio": {
|
||||||
"webfolio":{
|
"vulnerabilities":[
|
||||||
"vulnerabilities":[
|
{
|
||||||
{
|
"id":2993,
|
||||||
"id":2993,
|
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
"references": {
|
||||||
"references":"http://seclists.org/fulldisclosure/2013/Dec/135",
|
"url": "http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||||
"osvdb":"101101",
|
"osvdb":"101101"
|
||||||
"created_at":"2014-07-28T12:10:07.000Z",
|
},
|
||||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
"created_at":"2014-07-28T12:10:07.000Z",
|
||||||
}
|
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
{
|
{
|
||||||
"id": "3911",
|
"id": "3911",
|
||||||
"title": "Vuln Title",
|
"title": "Vuln Title",
|
||||||
"url": "Ref 1,Ref 2",
|
"references":{
|
||||||
"secunia": "secunia",
|
"url": "Ref 1,Ref 2",
|
||||||
"osvdb": "osvdb",
|
"secunia": "secunia",
|
||||||
"cve": "2011-001",
|
"osvdb": "osvdb",
|
||||||
"metasploit": "exploit/ex1",
|
"cve": "2011-001",
|
||||||
"exploitdb": "exploitdb",
|
"metasploit": "exploit/ex1",
|
||||||
|
"exploitdb": "exploitdb"
|
||||||
|
},
|
||||||
"created_at": "2014-07-28T12:10:45.000Z",
|
"created_at": "2014-07-28T12:10:45.000Z",
|
||||||
"updated_at": "2014-07-28T12:10:45.000Z",
|
"updated_at": "2014-07-28T12:10:45.000Z",
|
||||||
"type": "CSRF",
|
"type": "CSRF",
|
||||||
|
|||||||
0
spec/samples/common/models/wp_item/error_log
Executable file → Normal file
0
spec/samples/common/models/wp_item/error_log
Executable file → Normal file
0
spec/samples/common/models/wp_item/versionable/simple-login-lockdown-0.4.txt
Executable file → Normal file
0
spec/samples/common/models/wp_item/versionable/simple-login-lockdown-0.4.txt
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user