Compare commits

..

120 Commits
2.9.3 ... v2

Author SHA1 Message Date
Erwan
3a94b8362a Delete Gemfile.lock 2020-07-28 11:35:45 +02:00
Ryan Dewhurst
a25b493064 Merge pull request #1211 from sudoaza/master
Fixing error on missformated rss
2018-09-19 13:17:17 +02:00
aza
2acf88d83e fixing error on missformated rss 2018-09-17 01:44:36 +02:00
Ryan Dewhurst
baf3b4bc2b Add command line gif 2018-08-30 15:09:49 +02:00
Ryan Dewhurst
750411d9e1 Add Patreon shield/badge 2018-08-24 15:40:44 +02:00
Christian Mehlmauer
aa7b922d30 gem update 2018-07-17 17:59:01 +02:00
Christian Mehlmauer
fd660632e0 Update version for dev branch 2018-06-18 07:46:23 +02:00
Ryan Dewhurst
c7df7265ab Ready for 2.9.4 release #1187 2018-06-15 09:40:06 +02:00
Ryan Dewhurst
42685a45b3 Missing space 2018-06-08 09:58:07 +02:00
Ryan Dewhurst
ce5d26a220 Enhacements to sql export code. Thanks to javiercasares for list. 2018-06-08 09:56:28 +02:00
Ryan Dewhurst
0e73774bd9 Add check for .sql backup files 2018-06-07 17:17:39 +02:00
Christian Mehlmauer
85b491472a revert, just a test 2018-05-30 23:35:19 +02:00
Christian Mehlmauer
4b382acbad change twitter handle 2018-05-30 23:32:46 +02:00
Ryan Dewhurst
12d15bfc7e Update data.zip file 2018-05-30 14:39:58 +02:00
Ryan Dewhurst
ea1b6b9c17 Update version information 2018-05-30 14:39:31 +02:00
Ryan Dewhurst
5cb2d16601 Remove Gemnasium as deprecated 2018-05-30 12:56:25 +02:00
Christian Mehlmauer
913717bcf7 update gems 2018-05-30 12:51:50 +02:00
Ryan Dewhurst
99fe1855d9 Output is not plugin specific 2018-05-23 14:54:34 +02:00
Ryan Dewhurst
e2eb94be22 Grammar 2018-05-23 14:46:28 +02:00
Ryan Dewhurst
aca1b487ba Remove spacer in output 2018-05-23 14:44:53 +02:00
Ryan Dewhurst
5820c53d0f More informative output 2018-05-23 14:32:04 +02:00
Christian Mehlmauer
9298758acd Merge pull request #1182 from g0tmi1k/users
Multiple Features
2018-05-22 12:40:52 +02:00
g0tmi1k
a981c2b17b @FireFart's suggestions 2018-05-22 10:06:57 +01:00
g0tmi1k
a783b53107 Fix grammar
..and bots
2018-05-15 11:17:03 +01:00
g0tmi1k
cf2881fda6 Fix bots issues?
...Happy now? Please?
2018-05-15 10:47:55 +01:00
g0tmi1k
59368a72bd Don't fail silent. 2018-05-15 10:39:16 +01:00
g0tmi1k
439900a1ea Misc fixes 2018-05-15 09:05:58 +01:00
g0tmi1k
44557797b0 Update data.zip location to be $HOME 2018-05-15 08:19:44 +01:00
g0tmi1k
ba065d5974 ...Removed too much fat. 2018-05-15 08:09:24 +01:00
g0tmi1k
105e9cbcac Sorted out .*ignore & *files + removed some fat 2018-05-15 07:52:40 +01:00
g0tmi1k
fe277c1e89 Make travis happy 2018-05-15 07:12:02 +01:00
g0tmi1k
b5e3e6280e Trying to make code climate happier 2018-05-14 18:08:42 +01:00
g0tmi1k
f90a64ce81 Tried to make code climate happy 2018-05-14 17:56:49 +01:00
g0tmi1k
b9fa1e3587 Misc fixes and typos 2018-05-14 16:37:14 +01:00
g0tmi1k
4333ecb989 Check for sitemaps (using /robots.txt) 2018-05-14 16:36:52 +01:00
g0tmi1k
715d3d4ad6 Moved http response to a function 2018-05-14 16:35:41 +01:00
g0tmi1k
38f70a88ae Follow any redirections (e.g. http -> https) 2018-05-14 16:17:12 +01:00
g0tmi1k
4b4b968710 Check HTTP status of each value in /robots.txt 2018-05-14 15:57:33 +01:00
g0tmi1k
3b94fc49a7 Fix EOL issue when checking /robots.txt 2018-05-14 15:12:35 +01:00
g0tmi1k
e41aab3a80 Re-worked off-line update only as a fall back (when possible) 2018-05-14 15:12:20 +01:00
g0tmi1k
9450ba6cc5 Add RSS author information 2018-05-14 13:44:02 +01:00
g0tmi1k
ae3c164350 Improved API output results 2018-05-14 13:43:49 +01:00
g0tmi1k
24e6820a90 Clean up wording 2018-05-14 13:43:33 +01:00
g0tmi1k
0e05f77fb7 Made offline extraction more verbose 2018-05-14 13:37:34 +01:00
g0tmi1k
de960ff9db Fix offline extraction zip bug 2018-05-11 18:18:19 +01:00
g0tmi1k
1d0128af72 Move spacer to a function 2018-05-11 18:07:57 +01:00
g0tmi1k
285b1a1733 Cleaner output and fix a typo 2018-05-11 17:10:02 +01:00
g0tmi1k
ab67816dd9 Check for API access and /wp-json/'s users output 2018-05-11 17:01:06 +01:00
g0tmi1k
fea6665876 Re-order output around slightly 2018-05-11 16:59:25 +01:00
g0tmi1k
6cbc8c9924 Clean up some output confusion 2018-05-11 16:58:47 +01:00
g0tmi1k
f542a50213 Remove debug statement 2018-05-11 12:24:11 +01:00
g0tmi1k
fa430606ce Move the last item to ~/.wpscan/ 2018-05-11 11:25:18 +01:00
g0tmi1k
05d27c64be Check location before using them 2018-05-11 11:21:14 +01:00
g0tmi1k
0cd680bb29 Add dev information to file locations 2018-05-11 11:20:58 +01:00
g0tmi1k
ced94a7338 Fix up .gitignore 2018-05-11 11:20:20 +01:00
g0tmi1k
b65a4d0a60 Fix up gemfile 2018-05-11 11:20:03 +01:00
g0tmi1k
2b85b44bd1 Add offline database update support 2018-05-11 11:19:51 +01:00
g0tmi1k
991c87a89e Fix inconsistencies with line endings 2018-05-09 16:35:54 +01:00
g0tmi1k
37a72f0c72 Add /.well-known/security.txt check
See https://securitytxt.org/
2018-05-09 16:34:30 +01:00
g0tmi1k
6c0a21c80d Add /humans.txt check
See http://humanstxt.org/
2018-05-09 16:33:44 +01:00
g0tmi1k
dc48008d43 Bug with user-agent being shown 2018-05-09 16:16:18 +01:00
g0tmi1k
5720d29492 Fix inconsistencies with line endings 2018-05-09 16:11:09 +01:00
g0tmi1k
358f3d59d8 Say when to use --force 2018-05-09 16:04:01 +01:00
g0tmi1k
b6c6a46d25 Remove un-needed single quotes in output 2018-05-09 13:58:23 +01:00
g0tmi1k
25c393d557 gitignore cleanup 2018-05-09 13:58:04 +01:00
g0tmi1k
435fb34233 Check for user-agents.txt before using it 2018-05-09 13:15:12 +01:00
g0tmi1k
2c40913a64 Misc wording fixes 2018-05-09 13:14:41 +01:00
g0tmi1k
e437b952da Move timthumbs.txt to all the other data.zip files 2018-05-09 13:14:05 +01:00
g0tmi1k
282c595b38 Improve user prompt 2018-05-09 13:13:07 +01:00
g0tmi1k
c2c8d63e75 Show database date when updating 2018-05-09 13:12:27 +01:00
g0tmi1k
ad21d97d11 Grammar police! 2018-05-09 13:11:46 +01:00
g0tmi1k
5c27c78ed0 Add friendly reminder about using -u / --url 2018-05-09 13:10:34 +01:00
g0tmi1k
a53e9a5e12 Show the file being downloaded with verbose 2018-05-09 13:09:58 +01:00
g0tmi1k
c8036692ee Display user-agent with verbose mode (Handy with --random-agent) 2018-05-09 13:09:33 +01:00
Ryan Dewhurst
b9535a3648 Merge pull request #1180 from g0tmi1k/fixes
Stop trying to execute when it shouldn't
2018-05-09 10:05:14 +02:00
Ryan Dewhurst
651c364fa9 Merge pull request #1181 from g0tmi1k/users
Add a quick message about doing more wordpress users to usage
2018-05-09 10:03:49 +02:00
g0tmi1k
958410d4c9 Add a quick message about doing more wordpress users to usage 2018-05-08 17:19:33 +01:00
g0tmi1k
e9fba126d2 Stop trying to execute when it shouldn't 2018-05-08 17:14:48 +01:00
Christian Mehlmauer
95d39cce5a resolve 2018-05-08 07:53:45 +02:00
Christian Mehlmauer
32d9afdf9b update 2018-05-08 07:52:51 +02:00
Christian Mehlmauer
7e9a4168ff update 2018-05-08 07:50:32 +02:00
Christian Mehlmauer
9d6415a89b update gems 2018-03-28 00:25:48 +02:00
erwanlr
1499b07176 Fixes #1152 2018-01-30 19:59:41 +00:00
Christian Mehlmauer
9c7188a312 Merge branch 'master' of github.com:wpscanteam/wpscan 2018-01-11 07:25:05 +01:00
Christian Mehlmauer
b63e28c150 update readme 2018-01-11 07:24:51 +01:00
ethicalhack3r
50d48902cf Happy New Year! 2018-01-09 17:14:42 +01:00
Christian Mehlmauer
aa6899cbc5 ruby upgrade 2017-12-27 00:50:33 +01:00
Christian Mehlmauer
94e6b2eab6 upgrade ruby 2017-12-27 00:41:04 +01:00
erwanlr
54c0e79c58 Fixes #1154 2017-12-11 09:21:56 +00:00
erwanlr
859d7f1c60 Fixes spec, Ref #1147 2017-12-11 09:08:25 +00:00
Christian Mehlmauer
166112209e fix #1147 2017-12-06 19:18:15 +01:00
Christian Mehlmauer
952395d0c1 try to fix travis 2017-11-25 16:37:30 +01:00
Christian Mehlmauer
c7061f8a51 try to fix travis 2017-11-25 16:34:01 +01:00
Erwan
0c71bce221 Fix #1149 2017-11-24 08:43:20 +00:00
Christian Mehlmauer
b2b4eebd78 Merge branch 'master' of github.com:wpscanteam/wpscan 2017-11-14 19:41:24 +01:00
Christian Mehlmauer
5257a8b997 update 2017-11-14 19:41:15 +01:00
ethicalhack3r
9844f9d8ab Remove --max-threads option from output. Fix #1142 2017-11-08 10:59:33 +01:00
Christian Mehlmauer
000f275263 update bundler 2017-11-01 19:47:14 +01:00
Christian Mehlmauer
e5077c490a Merge branch 'master' of github.com:wpscanteam/wpscan 2017-10-22 00:36:25 +02:00
Christian Mehlmauer
d76968c15f update 2017-10-22 00:36:16 +02:00
Ryan Dewhurst
289ef5b0dd Remove some known issues. Fix #1141 2017-10-11 10:51:19 +02:00
Ryan Dewhurst
7ec227873c Update wording 2017-10-11 10:49:43 +02:00
Ryan Dewhurst
1deccfd477 Remove space 2017-10-11 10:40:10 +02:00
Ryan Dewhurst
286e6bd51a Update banner 2017-10-11 10:35:11 +02:00
Ryan Dewhurst
8167fa2e17 Remove CREDITS file 2017-10-11 10:34:17 +02:00
Ryan Dewhurst
c960df0bb1 Update copyright dates 2017-10-11 10:31:12 +02:00
Christian Mehlmauer
ebf8d31c6c specs 2017-10-09 12:59:43 +02:00
Christian Mehlmauer
082ae650fc specs 2017-10-09 12:53:18 +02:00
Christian Mehlmauer
2f5599c863 specs 2017-10-09 12:43:35 +02:00
Christian Mehlmauer
a764bdd993 update 2017-10-09 12:30:28 +02:00
Christian Mehlmauer
ef46d2c956 update readme and changelog files 2017-10-09 12:26:09 +02:00
Christian Mehlmauer
d2c2c1defb update 2017-09-18 20:40:51 +02:00
ethicalhack3r
dede023ec8 Update to Ruby 2.4.2 2017-09-14 19:50:31 +02:00
ethicalhack3r
d8a9b3aa77 Only show readme.html output when wp <= 4.8 #1127 2017-09-14 19:12:37 +02:00
Christian Mehlmauer
ad364e6a2e bundle update 2017-09-04 20:01:19 +02:00
Christian Mehlmauer
523954e507 bundle update 2017-08-11 10:08:25 +02:00
Christian Mehlmauer
872bbdb8e0 more output 2017-08-01 18:19:03 +02:00
Christian Mehlmauer
3ca8727b64 Merge branch 'master' of github.com:wpscanteam/wpscan 2017-08-01 18:15:50 +02:00
Christian Mehlmauer
1d3ca87772 better output 2017-08-01 18:15:37 +02:00
Christian Mehlmauer
90c42f42a1 Update README.md 2017-07-21 13:25:38 +02:00
48 changed files with 1150 additions and 384 deletions

View File

@@ -1,21 +1,21 @@
git/
bundle/
.idea/
.yardoc/
cache/
coverage/
spec/
dev/
.* .*
**/*.md bin/
dev/
spec/
*.md *.md
Dockerfile Dockerfile
## TEMP
.idea/
.yardoc/
bundle/
cache/
coverage/
git/
**/*.md
**/*.orig **/*.orig
*.orig *.orig
CREDITS CREDITS
data.zip data.zip
DISCLAIMER.txt DISCLAIMER.txt
example.conf.json example.conf.json
bin/
log.txt

29
.gitignore vendored
View File

@@ -1,16 +1,21 @@
# WPScan (If not using ~/.wpscan/)
cache/
data/
log.txt
output.txt
# WPScan (Deployment)
debug.log
rspec_results.html
wordlist.txt
# OS/IDE Rubbish
coverage/
.yardoc/
.idea/
*.sublime-*
.*.swp
.ash_history .ash_history
cache
coverage
.bundle .bundle
.DS_Store .DS_Store
.DS_Store? .DS_Store?
*.sublime-*
.idea
.*.swp
log.txt
.yardoc
debug.log
wordlist.txt
rspec_results.html
data/
vendor/

View File

@@ -1 +1 @@
2.4.1 2.5.1

View File

@@ -13,13 +13,18 @@ rvm:
- 2.3.2 - 2.3.2
- 2.3.3 - 2.3.3
- 2.4.1 - 2.4.1
- 2.4.2
- 2.5.0
- 2.5.1
- ruby-head
before_install: before_install:
- "env" - "env"
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc" - "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
- "gem install bundler" - "gem install bundler"
- "bundler --version" - "gem regenerate_binstubs"
- "bundle --version"
before_script: before_script:
- "unzip -o $TRAVIS_BUILD_DIR/data.zip -d $TRAVIS_BUILD_DIR" - "unzip -o $TRAVIS_BUILD_DIR/data.zip -d $HOME/.wpscan/"
script: script:
- "bundle exec rspec" - "bundle exec rspec"
notifications: notifications:

View File

@@ -1,6 +1,35 @@
# Changelog # Changelog
## Master ## Master
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.9.3...master) [Work in progress](https://github.com/wpscanteam/wpscan/compare/2.9.4...master)
## Version 2.9.4
Released: 2018-06-15
* Updated dependencies and required ruby version
* Improved CLI output
* Only show readme.html output when wp <= 4.8 #1127
* Cleanup README.md
* Fix bug "undefined method 'identifier' for nil:NilClass" #1149
* Since WP 4.7 readme.html only shows major version #1152
* Add checks for humans.txt and security.text (Thank you @g0tmi1k!)
* Add offline database update support (Thank you @g0tmi1k!)
* Check for API access and /wp-json/'s users output (Thank you @g0tmi1k!)
* Add RSS author information (Thank you @g0tmi1k!)
* Check HTTP status of each value in /robots.txt (Thank you @g0tmi1k!)
* Follow any redirections (e.g. http -> https) (Thank you @g0tmi1k!)
* Lots of other enhancements by @g0tmi1k & WPScan Team
* Database export file enumeration.
WPScan Database Statistics:
* Total tracked wordpresses: 319
* Total tracked plugins: 74896
* Total tracked themes: 16666
* Total vulnerable wordpresses: 305
* Total vulnerable plugins: 1645
* Total vulnerable themes: 286
* Total wordpress vulnerabilities: 8327
* Total plugin vulnerabilities: 2603
* Total theme vulnerabilities: 352
## Version 2.9.3 ## Version 2.9.3
Released: 2017-07-19 Released: 2017-07-19

21
CREDITS
View File

@@ -1,21 +0,0 @@
**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 team@wpscan.org.
*WPScan Team*
Erwan.LR - @erwan_lr - (Project Developer)
Christian Mehlmauer - @_FireFart_ - (Project Developer)
Peter van der Laan - pvdl - (Project Developer)
Ryan Dewhurst - @ethicalhack3r (Project Lead)
*Other Contributors*
Henri Salo AKA fgeek - Reported lots of vulnerabilities
Alip AKA Undead - alip.aswalid at gmail.com
michee08 - Reported and gave potential solutions to bugs
Callum Pember - Implemented proxy support - callumpember at gmail.com
g0tmi1k - Additional timthumb checks + bug reports
Melvin Lammerts - Reported a couple of fake vulnerabilities - melvin at 12k.nl
Paolo Perego - @thesp0nge - Basic authentication
Gianluca Brindisi - @gbrindisi - Ex Project Developer

View File

@@ -1,29 +1,37 @@
FROM ruby:2.4-alpine FROM ruby:2.5-alpine
MAINTAINER WPScan Team <team@wpscan.org> LABEL maintainer="WPScan Team <team@wpscan.org>"
ARG BUNDLER_ARGS="--jobs=8 --without test" ARG BUNDLER_ARGS="--jobs=8 --without test"
# Add a new user
RUN adduser -h /wpscan -g WPScan -D wpscan RUN adduser -h /wpscan -g WPScan -D wpscan
# Setup gems
RUN echo "gem: --no-ri --no-rdoc" > /etc/gemrc RUN echo "gem: --no-ri --no-rdoc" > /etc/gemrc
COPY Gemfile /wpscan COPY Gemfile /wpscan
COPY Gemfile.lock /wpscan COPY Gemfile.lock /wpscan
# runtime dependencies # Runtime dependencies
RUN apk add --no-cache libcurl procps && \ RUN apk add --no-cache libcurl procps && \
# build dependencies # build dependencies
apk add --no-cache --virtual build-deps alpine-sdk ruby-dev libffi-dev zlib-dev && \ apk add --no-cache --virtual build-deps alpine-sdk ruby-dev libffi-dev zlib-dev && \
bundle install --system --gemfile=/wpscan/Gemfile $BUNDLER_ARGS && \ bundle install --system --gemfile=/wpscan/Gemfile $BUNDLER_ARGS && \
apk del --no-cache build-deps apk del --no-cache build-deps
# Copy over data & set permissions
COPY . /wpscan COPY . /wpscan
RUN chown -R wpscan:wpscan /wpscan RUN chown -R wpscan:wpscan /wpscan
USER wpscan # Switch directory
RUN /wpscan/wpscan.rb --update --verbose --no-color
WORKDIR /wpscan WORKDIR /wpscan
# Switch users
USER wpscan
# Update WPScan
RUN /wpscan/wpscan.rb --update --verbose --no-color
# Run WPScan
ENTRYPOINT ["/wpscan/wpscan.rb"] ENTRYPOINT ["/wpscan/wpscan.rb"]
CMD ["--help"] CMD ["--help"]

View File

@@ -1,11 +1,12 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'typhoeus', '>=1.1.2'
gem 'nokogiri', '>=1.7.0.1'
gem 'addressable', '>=2.5.0' gem 'addressable', '>=2.5.0'
gem 'yajl-ruby', '>=1.3.0' # Better JSON parser regarding memory usage gem 'nokogiri', '>=1.7.0.1'
gem 'terminal-table', '>=1.6.0'
gem 'ruby-progressbar', '>=1.8.1' gem 'ruby-progressbar', '>=1.8.1'
gem 'rubyzip', '>=1.2.1'
gem 'terminal-table', '>=1.6.0'
gem 'typhoeus', '>=1.1.2'
gem 'yajl-ruby', '>=1.3.0' # Better JSON parser regarding memory usage
group :test do group :test do
gem 'webmock', '>=2.3.2' gem 'webmock', '>=2.3.2'

View File

@@ -1,69 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
crack (0.4.3)
safe_yaml (~> 1.0.0)
diff-lcs (1.3)
docile (1.1.5)
ethon (0.10.1)
ffi (>= 1.3.0)
ffi (1.9.18)
hashdiff (0.3.4)
json (2.1.0)
mini_portile2 (2.2.0)
nokogiri (1.8.0)
mini_portile2 (~> 2.2.0)
public_suffix (2.0.5)
rspec (3.6.0)
rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-core (3.6.0)
rspec-support (~> 3.6.0)
rspec-expectations (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0)
rspec-its (1.2.0)
rspec-core (>= 3.0.0)
rspec-expectations (>= 3.0.0)
rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0)
rspec-support (3.6.0)
ruby-progressbar (1.8.1)
safe_yaml (1.0.4)
simplecov (0.14.1)
docile (~> 1.1.0)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.1)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
typhoeus (1.1.2)
ethon (>= 0.9.0)
unicode-display_width (1.3.0)
webmock (3.0.1)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
yajl-ruby (1.3.0)
PLATFORMS
ruby
DEPENDENCIES
addressable (>= 2.5.0)
nokogiri (>= 1.7.0.1)
rspec (>= 3.5.0)
rspec-its (>= 1.2.0)
ruby-progressbar (>= 1.8.1)
simplecov (>= 0.13.0)
terminal-table (>= 1.6.0)
typhoeus (>= 1.1.2)
webmock (>= 2.3.2)
yajl-ruby (>= 1.3.0)
BUNDLED WITH
1.14.6

View File

@@ -1,6 +1,6 @@
WPScan Public Source License WPScan Public Source License
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2016 WPScan Team. The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2018 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.
@@ -8,7 +8,7 @@ Cases that include commercialization of WPScan require a commercial, non-free li
1.1 “License” means this document. 1.1 “License” means this document.
1.2 “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns WPScan. 1.2 “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
1.3 “WPScan Team” means WPScans core developers, an updated list of whom can be found within the CREDITS file. 1.3 “WPScan Team” means WPScans core developers.
2. Commercialization 2. Commercialization

View File

@@ -1,16 +1,17 @@
![alt text](https://raw.githubusercontent.com/wpscanteam/wpscan/gh-pages/wpscan_logo_407x80.png "WPScan - WordPress Security Scanner") ![alt text](https://raw.githubusercontent.com/wpscanteam/wpscan/gh-pages/wpscan_logo_407x80.png "WPScan - WordPress Security Scanner")
[![Build Status](https://travis-ci.org/wpscanteam/wpscan.svg?branch=master)](https://travis-ci.org/wpscanteam/wpscan) [![Build Status](https://travis-ci.org/wpscanteam/wpscan.svg?branch=master)](https://travis-ci.org/wpscanteam/wpscan)
[![Code Climate](https://img.shields.io/codeclimate/github/wpscanteam/wpscan.svg)](https://codeclimate.com/github/wpscanteam/wpscan) [![Code Climate](https://img.shields.io/codeclimate/github/wpscanteam/wpscan.svg)](https://codeclimate.com/github/wpscanteam/wpscan)
[![Dependency Status](https://img.shields.io/gemnasium/wpscanteam/wpscan.svg)](https://gemnasium.com/wpscanteam/wpscan)
[![Docker Pulls](https://img.shields.io/docker/pulls/wpscanteam/wpscan.svg)](https://hub.docker.com/r/wpscanteam/wpscan/) [![Docker Pulls](https://img.shields.io/docker/pulls/wpscanteam/wpscan.svg)](https://hub.docker.com/r/wpscanteam/wpscan/)
[![Patreon Donate](https://img.shields.io/badge/patreon-donate-green.svg)](https://www.patreon.com/wpscan)
![alt text](https://wpscan.org/images/tty.gif "WPScan Screen Recording")
# LICENSE # LICENSE
## WPScan Public Source License ## WPScan Public Source License
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2016 WPScan Team. The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2018 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.
@@ -102,6 +103,10 @@ Windows is not supported
We suggest you use our official Docker image from https://hub.docker.com/r/wpscanteam/wpscan/ to avoid installation problems. We suggest you use our official Docker image from https://hub.docker.com/r/wpscanteam/wpscan/ to avoid installation problems.
# DOCKER # DOCKER
## Install Docker
[https://docs.docker.com/engine/installation/](https://docs.docker.com/engine/installation/)
## Get the image
Pull the repo with `docker pull wpscanteam/wpscan` Pull the repo with `docker pull wpscanteam/wpscan`
## Start WPScan ## Start WPScan
@@ -122,6 +127,8 @@ Mount a local wordlist to the docker container and start a bruteforce attack for
docker run -it --rm -v ~/wordlists:/wordlists wpscanteam/wpscan --url https://yourblog.com --wordlist /wordlists/crackstation.txt --username admin docker run -it --rm -v ~/wordlists:/wordlists wpscanteam/wpscan --url https://yourblog.com --wordlist /wordlists/crackstation.txt --username admin
``` ```
(This mounts the host directory `~/wordlists` to the container in the path `/wordlists`)
Use logfile option Use logfile option
``` ```
# the file must exist prior to starting the container, otherwise docker will create a directory with the filename # the file must exist prior to starting the container, otherwise docker will create a directory with the filename
@@ -129,15 +136,13 @@ touch ~/FILENAME
docker run -it --rm -v ~/FILENAME:/wpscan/output.txt wpscanteam/wpscan --url https://yourblog.com --log /wpscan/output.txt docker run -it --rm -v ~/FILENAME:/wpscan/output.txt wpscanteam/wpscan --url https://yourblog.com --log /wpscan/output.txt
``` ```
(This mounts the host directory `~/wordlists` to the container in the path `/wordlists`)
Published on https://hub.docker.com/r/wpscanteam/wpscan/ Published on https://hub.docker.com/r/wpscanteam/wpscan/
# Manual install # Manual install
## Prerequisites ## Prerequisites
- Ruby >= 2.1.9 - Recommended: 2.4.1 - Ruby >= 2.1.9 - Recommended: 2.5.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
@@ -174,8 +179,8 @@ https://rvm.io/integration/gnome-terminal#integrating-rvm-with-gnome-terminal
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.4.1 rvm install 2.5.1
rvm use 2.4.1 --default rvm use 2.5.1 --default
echo "gem: --no-ri --no-rdoc" > ~/.gemrc echo "gem: --no-ri --no-rdoc" > ~/.gemrc
git clone https://github.com/wpscanteam/wpscan.git git clone https://github.com/wpscanteam/wpscan.git
cd wpscan cd wpscan
@@ -190,39 +195,6 @@ https://rvm.io/integration/gnome-terminal#integrating-rvm-with-gnome-terminal
# KNOWN ISSUES # KNOWN ISSUES
- Typhoeus segmentation fault
Update cURL to version => 7.21 (may have to install from source)
- Proxy not working
Update cURL to version => 7.21.7 (may have to install from source).
Installation from sources :
Grab the sources from http://curl.haxx.se/download.html
Decompress the archive
Open the folder with the extracted files
Run ./configure
Run make
Run sudo make install
Run sudo ldconfig
- cannot load such file -- readline:
sudo aptitude install libreadline5-dev libncurses5-dev
Then, open the directory of the readline gem (you have to locate it)
cd ~/.rvm/src/ruby-XXXX/ext/readline
ruby extconf.rb
make
make install
See [http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/](http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/) for more details
- no such file to load -- rubygems - no such file to load -- rubygems
```update-alternatives --config ruby``` ```update-alternatives --config ruby```
@@ -282,7 +254,6 @@ https://rvm.io/integration/gnome-terminal#integrating-rvm-with-gnome-terminal
--request-timeout <request-timeout> Request Timeout. --request-timeout <request-timeout> Request Timeout.
--connect-timeout <connect-timeout> Connect Timeout. --connect-timeout <connect-timeout> Connect Timeout.
--threads | -t <number of threads> The number of threads to use when multi-threading requests. --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. --throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.
--help | -h This help screen. --help | -h This help screen.
--verbose | -v Verbose output. --verbose | -v Verbose output.

View File

@@ -9,4 +9,4 @@ done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
cd $DIR/../ cd $DIR/../
docker run -it --rm -v "$DIR/../":/wpscan -w /wpscan ruby:2.4 bundle update docker run --rm -v "$DIR/../":/usr/src/app -w /usr/src/app ruby:2.5-alpine /bin/sh -c "gem install bundler; bundle lock --update"

BIN
data.zip

Binary file not shown.

2
data/.gitignore vendored
View File

@@ -1,2 +0,0 @@
*
!.gitignore

View File

@@ -126,7 +126,7 @@ class Browser
def merge_request_params(params = {}) def merge_request_params(params = {})
if @proxy if @proxy
params.merge!(proxy: @proxy) params.merge!(proxy: @proxy)
params.merge!(proxyauth: @proxy_auth) if @proxy_auth params.merge!(proxyuserpwd: @proxy_auth) if @proxy_auth
end end
if @basic_auth if @basic_auth

View File

@@ -26,6 +26,10 @@ class CacheFileStore
unless Dir.exist?(@storage_path) unless Dir.exist?(@storage_path)
FileUtils.mkdir_p(@storage_path) FileUtils.mkdir_p(@storage_path)
end end
unless Pathname.new(@storage_path).writable?
fail "#{@storage_path} is not writable"
end
end end
def clean def clean

View File

@@ -95,7 +95,7 @@ class WpItems < Array
code = tag.text.to_s code = tag.text.to_s
next if code.empty? next if code.empty?
if ! code.valid_encoding? if !code.valid_encoding?
code = code.encode('UTF-16be', :invalid => :replace, :replace => '?').encode('UTF-8') code = code.encode('UTF-16be', :invalid => :replace, :replace => '?').encode('UTF-8')
end end

View File

@@ -9,7 +9,7 @@ class WpUsers < WpItems
# @return [ void ] # @return [ void ]
def output(options = {}) def output(options = {})
rows = [] rows = []
headings = ['Id', 'Login', 'Name'] headings = ['ID', 'Login', 'Name']
headings << 'Password' if options[:show_password] headings << 'Password' if options[:show_password]
remove_junk_from_display_names remove_junk_from_display_names

View File

@@ -1,36 +1,34 @@
# encoding: UTF-8 # encoding: UTF-8
LIB_DIR = File.expand_path(File.join(__dir__, '..')) # Location directories
ROOT_DIR = File.expand_path(File.join(LIB_DIR, '..')) # expand_path is used to get "wpscan/" instead of "wpscan/lib/../" LIB_DIR = File.expand_path(File.join(__dir__, '..')) # wpscan/lib/
DATA_DIR = File.join(ROOT_DIR, 'data') ROOT_DIR = File.expand_path(File.join(LIB_DIR, '..')) # wpscan/ - expand_path is used to get "wpscan/" instead of "wpscan/lib/../"
CONF_DIR = File.join(ROOT_DIR, 'conf') USER_DIR = File.expand_path(Dir.home) # ~/
CACHE_DIR = File.join(ROOT_DIR, 'cache')
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan')
UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater')
COMMON_LIB_DIR = File.join(LIB_DIR, 'common')
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models')
COLLECTIONS_LIB_DIR = File.join(COMMON_LIB_DIR, 'collections')
DEFAULT_LOG_FILE = File.join(ROOT_DIR, 'log.txt') # Core WPScan directories
CACHE_DIR = File.join(USER_DIR, '.wpscan/cache') # ~/.wpscan/cache/
DATA_DIR = File.join(USER_DIR, '.wpscan/data') # ~/.wpscan/data/
CONF_DIR = File.join(USER_DIR, '.wpscan/conf') # ~/.wpscan/conf/ - Not used ATM (only ref via ./spec/ for travis)
COMMON_LIB_DIR = File.join(LIB_DIR, 'common') # wpscan/lib/common/
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan') # wpscan/lib/wpscan/
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models') # wpscan/lib/common/models/
# Plugins directories # Core WPScan files
COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins') DEFAULT_LOG_FILE = File.join(USER_DIR, '.wpscan/log.txt') # ~/.wpscan/log.txt
WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM DATA_FILE = File.join(ROOT_DIR, 'data.zip') # wpscan/data.zip
# Data files # WPScan Data files (data.zip)
WORDPRESSES_FILE = File.join(DATA_DIR, 'wordpresses.json') LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update') # ~/.wpscan/data/.last_update
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.json') PLUGINS_FILE = File.join(DATA_DIR, 'plugins.json') # ~/.wpscan/data/plugins.json
THEMES_FILE = File.join(DATA_DIR, 'themes.json') THEMES_FILE = File.join(DATA_DIR, 'themes.json') # ~/.wpscan/data/themes.json
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml') TIMTHUMBS_FILE = File.join(DATA_DIR, 'timthumbs.txt') # ~/.wpscan/data/timthumbs.txt
LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml') USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt') # ~/.wpscan/data/user-agents.txt
WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd') WORDPRESSES_FILE = File.join(DATA_DIR, 'wordpresses.json') # ~/.wpscan/data/wordpresses.json
LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd') WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml') # ~/.wpscan/data/wp_versions.xml
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update')
MIN_RUBY_VERSION = '2.1.9' MIN_RUBY_VERSION = '2.1.9'
WPSCAN_VERSION = '2.9.3' WPSCAN_VERSION = '2.9.5-dev'
$LOAD_PATH.unshift(LIB_DIR) $LOAD_PATH.unshift(LIB_DIR)
$LOAD_PATH.unshift(WPSCAN_LIB_DIR) $LOAD_PATH.unshift(WPSCAN_LIB_DIR)
@@ -50,6 +48,7 @@ def windows?
end end
require 'environment' require 'environment'
require 'zip'
def escape_glob(s) def escape_glob(s)
s.gsub(/[\\\{\}\[\]\*\?]/) { |x| '\\' + x } s.gsub(/[\\\{\}\[\]\*\?]/) { |x| '\\' + x }
@@ -78,13 +77,39 @@ def add_trailing_slash(url)
url =~ /\/$/ ? url : "#{url}/" url =~ /\/$/ ? url : "#{url}/"
end end
def missing_db_file? def missing_db_files?
DbUpdater::FILES.each do |db_file| DbUpdater::FILES.each do |db_file|
return true unless File.exist?(File.join(DATA_DIR, db_file)) return true unless File.exist?(File.join(DATA_DIR, db_file))
end end
false false
end end
# Find data.zip?
def has_db_zip?
return File.exist?(DATA_FILE)
end
# Extract data.zip
def extract_db_zip
# Create data folder
FileUtils.mkdir_p(DATA_DIR)
Zip::File.open(DATA_FILE) do |zip_file|
zip_file.each do |f|
# Feedback to the user
#puts "[+] Extracting: #{File.basename(f.name)}"
f_path = File.join(DATA_DIR, File.basename(f.name))
# Delete if already there
#puts "[+] Deleting: #{File.basename(f.name)}" if File.exist?(f_path)
FileUtils.rm(f_path) if File.exist?(f_path)
# Extract
zip_file.extract(f, f_path)
end
end
end
def last_update def last_update
date = nil date = nil
if File.exists?(LAST_UPDATE_FILE) if File.exists?(LAST_UPDATE_FILE)
@@ -94,6 +119,7 @@ def last_update
date date
end end
# Was it 5 days ago?
def update_required? def update_required?
date = last_update date = last_update
day_seconds = 24 * 60 * 60 day_seconds = 24 * 60 * 60
@@ -161,7 +187,7 @@ def banner
puts ' WordPress Security Scanner by the WPScan Team ' puts ' WordPress Security Scanner by the WPScan Team '
puts " Version #{WPSCAN_VERSION}" puts " Version #{WPSCAN_VERSION}"
puts ' Sponsored by Sucuri - https://sucuri.net' puts ' Sponsored by Sucuri - https://sucuri.net'
puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, pvdl, @_FireFart_' puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_'
puts '_______________________________________________________________' puts '_______________________________________________________________'
puts puts
end end
@@ -253,14 +279,26 @@ end
# @return [ String ] A random user-agent from data/user-agents.txt # @return [ String ] A random user-agent from data/user-agents.txt
def get_random_user_agent def get_random_user_agent
user_agents = [] user_agents = []
# If we can't access the file, die
raise('[ERROR] Missing user-agent data. Please re-run with just --update.') unless File.exist?(USER_AGENTS_FILE)
# Read in file
f = File.open(USER_AGENTS_FILE, 'r') f = File.open(USER_AGENTS_FILE, 'r')
# Read every line in the file
f.each_line do |line| f.each_line do |line|
# ignore comments # Remove any End of Line issues, and leading/trailing spaces
line = line.strip.chomp
# Ignore empty files and comments
next if line.empty? or line =~ /^\s*(#|\/\/)/ next if line.empty? or line =~ /^\s*(#|\/\/)/
# Add to array
user_agents << line.strip user_agents << line.strip
end end
# Close file handler
f.close f.close
# return ransom user-agent
# Return random user-agent
user_agents.sample user_agents.sample
end end
@@ -274,3 +312,21 @@ end
def url_encode(str) def url_encode(str)
CGI.escape(str).gsub("+", "%20") CGI.escape(str).gsub("+", "%20")
end end
# Check valid JSON?
def valid_json?(json)
JSON.parse(json)
return true
rescue JSON::ParserError => e
return false
end
# Get the HTTP response code
def get_http_status(url)
Browser.get(url.to_s).code
end
# Check to see if we need a "s"
def grammar_s(size)
size.to_i >= 2 ? "s" : ""
end

View File

@@ -13,8 +13,13 @@ class DbUpdater
def initialize(repo_directory) def initialize(repo_directory)
@repo_directory = repo_directory @repo_directory = repo_directory
fail "#{repo_directory} is not writable" unless \ unless Dir.exist?(@repo_directory)
Pathname.new(repo_directory).writable? FileUtils.mkdir_p(@repo_directory)
end
unless Pathname.new(@repo_directory).writable?
fail "#{@repo_directory} is not writable"
end
end end
# @return [ Hash ] The params for Typhoeus::Request # @return [ Hash ] The params for Typhoeus::Request
@@ -83,7 +88,7 @@ class DbUpdater
def update(verbose = false) def update(verbose = false)
FILES.each do |filename| FILES.each do |filename|
begin begin
puts "[+] Checking #{filename}" if verbose puts "[+] Checking: #{filename}" if verbose
db_checksum = remote_file_checksum(filename) db_checksum = remote_file_checksum(filename)
# Checking if the file needs to be updated # Checking if the file needs to be updated
@@ -95,7 +100,7 @@ class DbUpdater
puts ' [i] Needs to be updated' if verbose puts ' [i] Needs to be updated' if verbose
create_backup(filename) create_backup(filename)
puts ' [i] Backup Created' if verbose puts ' [i] Backup Created' if verbose
puts ' [i] Downloading new file' if verbose puts " [i] Downloading new file: #{remote_file_url(filename)}" 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 puts " [i] Database File Checksum : #{db_checksum}" if verbose

View File

@@ -5,27 +5,6 @@ class WpItem
# @uri is used instead of #uri to avoid the presence of the :path into it # @uri is used instead of #uri to avoid the presence of the :path into it
module Infos module Infos
# @return [ Boolean ]
def has_readme?
!readme_url.nil?
end
# @return [ String,nil ] The url to the readme file, nil if not found
def readme_url
# See https://github.com/wpscanteam/wpscan/pull/737#issuecomment-66375445
# for any question about the order
%w{readme.txt README.txt Readme.txt ReadMe.txt README.TXT readme.TXT}.each do |readme|
url = @uri.merge(readme).to_s
return url if url_is_200?(url)
end
nil
end
# @return [ Boolean ]
def has_changelog?
url_is_200?(changelog_url)
end
# Checks if the url status code is 200 # Checks if the url status code is 200
# #
# @param [ String ] url # @param [ String ] url
@@ -35,9 +14,34 @@ class WpItem
Browser.get(url).code == 200 Browser.get(url).code == 200
end end
# @return [ Boolean ]
def has_readme?
!readme_url.nil?
end
# @return [ String,nil ] The url to the readme file, nil if not found
def readme_url
# See https://github.com/wpscanteam/wpscan/pull/737#issuecomment-66375445
# for any question about the order
%w{readme.txt README.txt README.md readme.md Readme.txt}.each do |readme|
url = @uri.merge(readme).to_s
return url if url_is_200?(url)
end
nil
end
# @return [ Boolean ]
def has_changelog?
!changelog_url.nil?
end
# @return [ String ] The url to the changelog file # @return [ String ] The url to the changelog file
def changelog_url def changelog_url
@uri.merge('changelog.txt').to_s %w{changelog.txt CHANGELOG.md changelog.md}.each do |changelog|
url = @uri.merge(changelog).to_s
return url if url_is_200?(url)
end
nil
end end
# @return [ Boolean ] # @return [ Boolean ]

View File

@@ -23,7 +23,7 @@ class WpItem
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 the version installed. All of the past known vulnerabilities will be output to allow you to do your own manual investigation.')
end end
vulnerabilities.output vulnerabilities.output

View File

@@ -30,7 +30,7 @@ class WpTheme < WpItem
response = Browser.get_and_follow_location(target_uri.to_s) response = Browser.get_and_follow_location(target_uri.to_s)
# https + domain is optional because of relative links # https + domain is optional because of relative links
return unless response.body =~ %r{(?:https?://[^"']+/)?([^/\s]+)/themes/([^"'/]+)[^"']*/style.css}i return unless response.body =~ %r{(?:https?://[^"']+/)?([^"'/\s]+)/themes/([^"'/]+)[^"']*/style\.css}i
new( new(
target_uri, target_uri,

View File

@@ -168,11 +168,14 @@ class WpVersion < WpItem
# #
# @return [ String ] The version number # @return [ String ] The version number
def find_from_readme(target_uri) def find_from_readme(target_uri)
scan_url( version = scan_url(
target_uri, target_uri,
%r{<br />\sversion #{version_pattern}}i, %r{<br />\sversion #{version_pattern}}i,
'readme.html' 'readme.html'
) )
# Since WP >= 4.7, the Readme only contains the major version
VersionCompare.lesser?(version, '4.7') ? version : nil
end end
# Attempts to find the WordPress version from the sitemap.xml file. # Attempts to find the WordPress version from the sitemap.xml file.

View File

@@ -1,11 +1,19 @@
# encoding: UTF-8 # encoding: UTF-8
require 'web_site/robots_txt' require 'web_site/humans_txt'
require 'web_site/interesting_headers' require 'web_site/interesting_headers'
require 'web_site/robots_txt'
require 'web_site/security_txt'
require 'web_site/sitemap'
require 'web_site/sql_file_export'
class WebSite class WebSite
include WebSite::RobotsTxt include WebSite::HumansTxt
include WebSite::InterestingHeaders include WebSite::InterestingHeaders
include WebSite::RobotsTxt
include WebSite::SecurityTxt
include WebSite::Sitemap
include WebSite::SqlFileExport
attr_reader :uri attr_reader :uri
@@ -121,13 +129,6 @@ class WebSite
@error_404_hash @error_404_hash
end end
# Will try to find the rss url in the homepage
# Only the first one found is returned
def rss_url
homepage_body = Browser.get(@uri.to_s).body
homepage_body[%r{<link .* type="application/rss\+xml" .* href="([^"]+)" />}, 1]
end
# Only the first 700 bytes are checked to avoid the download # Only the first 700 bytes are checked to avoid the download
# of the whole file which can be very huge (like 2 Go) # of the whole file which can be very huge (like 2 Go)
# #

View File

@@ -0,0 +1,13 @@
# encoding: UTF-8
class WebSite
module HumansTxt
# Gets the humans.txt URL
# @return [ String ]
def humans_url
@uri.clone.merge('humans.txt').to_s
end
end
end

View File

@@ -18,49 +18,53 @@ class WebSite
# 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
return unless has_robots?
return_object = [] return_object = []
# Make request
response = Browser.get(robots_url.to_s) response = Browser.get(robots_url.to_s)
body = response.body body = response.body
# Get all allow and disallow urls # Get all allow and disallow urls
entries = body.scan(/^(?:dis)?allow:\s*(.*)$/i) entries = body.scan(/^(?:dis)?allow:\s*(.*)$/i)
# Did we get something?
if entries if entries
entries.flatten! # Remove any rubbish
entries.compact.sort! entries = clean_uri(entries)
entries.uniq!
# Sort
entries.sort!
# Wordpress URL
wordpress_path = @uri.path wordpress_path = @uri.path
# Each "boring" value as defined below, remove
RobotsTxt.known_dirs.each do |d| RobotsTxt.known_dirs.each do |d|
entries.delete(d) entries.delete(d)
# also delete when wordpress is installed in subdir # Also delete when wordpress is installed in subdir
dir_with_subdir = "#{wordpress_path}/#{d}".gsub(/\/+/, '/') dir_with_subdir = "#{wordpress_path}/#{d}".gsub(/\/+/, '/')
entries.delete(dir_with_subdir) entries.delete(dir_with_subdir)
end end
entries.each do |d| # Convert to full URIs
begin return_object = full_uri(entries)
temp = @uri.clone
temp.path = d.strip
rescue URI::Error
temp = d.strip
end end
return_object << temp.to_s return return_object
end
end
return_object
end end
protected protected
# Useful ~ "function do_robots()" -> https://github.com/WordPress/WordPress/blob/master/wp-includes/functions.php
#
# @return [ Array ] # @return [ Array ]
def self.known_dirs def self.known_dirs
%w{ %w{
/ /
/wp-admin/ /wp-admin/
/wp-admin/admin-ajax.php
/wp-includes/ /wp-includes/
/wp-content/ /wp-content/
} }
end end
end end
end end

View File

@@ -0,0 +1,13 @@
# encoding: UTF-8
class WebSite
module SecurityTxt
# Gets the security.txt URL
# @return [ String ]
def security_url
@uri.clone.merge('.well-known/security.txt').to_s
end
end
end

View File

@@ -0,0 +1,53 @@
# encoding: UTF-8
class WebSite
module Sitemap
# Checks if a sitemap.txt file exists
# @return [ Boolean ]
def has_sitemap?
# Make the request
response = Browser.get(sitemap_url)
# Make sure its HTTP 200
return false unless response.code == 200
# Is there a sitemap value?
result = response.body.scan(/^sitemap\s*:\s*(.*)$/i)
return true if result[0]
return false
end
# Get the robots.txt URL
# @return [ String ]
def sitemap_url
@uri.clone.merge('robots.txt').to_s
end
# Parse robots.txt
# @return [ Array ] URLs generated from robots.txt
def parse_sitemap
return_object = []
# Make request
response = Browser.get(sitemap_url.to_s)
# Get all allow and disallow urls
entries = response.body.scan(/^sitemap\s*:\s*(.*)$/i)
# Did we get something?
if entries
# Remove any rubbish
entries = clean_uri(entries)
# Sort
entries.sort!
# Convert to full URIs
return_object = full_uri(entries)
end
return return_object
end
end
end

View File

@@ -0,0 +1,35 @@
# encoding: UTF-8
class WebSite
module SqlFileExport
# Checks if a .sql file exists
# @return [ Array ]
def sql_file_export
export_files = []
self.sql_file_export_urls.each do |url|
response = Browser.get(url)
export_files << url if response.code == 200 && response.body =~ /INSERT INTO/
end
export_files
end
# Gets a .sql export file URL
# @return [ Array ]
def sql_file_export_urls
urls = []
host = @uri.host[/(^[\w|-]+)/,1]
files = ["#{host}.sql", "#{host}.sql.gz", "#{host}.zip", 'db.sql', 'site.sql', 'database.sql',
'data.sql', 'backup.sql', 'dump.sql', 'db_backup.sql', 'dbdump.sql', 'wordpress.sql', 'mysql.sql']
files.each do |file|
urls << @uri.clone.merge(file).to_s
end
urls
end
end
end

View File

@@ -1,22 +1,26 @@
# encoding: UTF-8 # encoding: UTF-8
require 'web_site' require 'web_site'
require 'wp_target/wp_readme' require 'wp_target/wp_api'
require 'wp_target/wp_registrable'
require 'wp_target/wp_config_backup' require 'wp_target/wp_config_backup'
require 'wp_target/wp_must_use_plugins'
require 'wp_target/wp_login_protection'
require 'wp_target/wp_custom_directories' require 'wp_target/wp_custom_directories'
require 'wp_target/wp_full_path_disclosure' require 'wp_target/wp_full_path_disclosure'
require 'wp_target/wp_login_protection'
require 'wp_target/wp_must_use_plugins'
require 'wp_target/wp_readme'
require 'wp_target/wp_registrable'
require 'wp_target/wp_rss'
class WpTarget < WebSite class WpTarget < WebSite
include WpTarget::WpReadme include WpTarget::WpAPI
include WpTarget::WpRegistrable
include WpTarget::WpConfigBackup include WpTarget::WpConfigBackup
include WpTarget::WpMustUsePlugins
include WpTarget::WpLoginProtection
include WpTarget::WpCustomDirectories include WpTarget::WpCustomDirectories
include WpTarget::WpFullPathDisclosure include WpTarget::WpFullPathDisclosure
include WpTarget::WpLoginProtection
include WpTarget::WpMustUsePlugins
include WpTarget::WpReadme
include WpTarget::WpRegistrable
include WpTarget::WpRSS
attr_reader :verbose attr_reader :verbose

View File

@@ -0,0 +1,86 @@
# encoding: UTF-8
class WpTarget < WebSite
module WpAPI
# Checks to see if the REST API is enabled
#
# This by default in a WordPress installation since 4.5+
# @return [ Boolean ]
def has_api?(url)
# Make the request
response = Browser.get(url)
# Able to view the output?
if valid_json?(response.body) && response.body != ''
# Read in JSON
data = JSON.parse(response.body)
# If there is nothing there, return false
if data.empty?
return false
# WAF/API disabled response
elsif data.include?('message') and data['message'] =~ /Only authenticated users can access the REST API/
return false
# Success!
elsif response.code == 200
return true
end
end
# Something went wrong
return false
end
# @return [ String ] The API/JSON URL
def json_url
@uri.merge('/wp-json/').to_s
end
# @return [ String ] The API/JSON URL to show users
def json_users_url
@uri.merge('/wp-json/wp/v2/users').to_s
end
# @return [ String ] The API/JSON URL to show users
def json_get_users(url)
# Variables
users = []
# Make the request
response = Browser.get(url)
# If not HTTP 200, return false
return false unless response.code == 200
# Able to view the output?
return false unless valid_json?(response.body)
# Read in JSON
data = JSON.parse(response.body)
# If there is nothing there, return false
return false if data.empty?
# Add to array
data.each do |child|
row = [ child['id'], child['name'], child['link'] ]
users << row
end
# Sort and uniq
users = users.sort.uniq
if users and users.size >= 1
# Feedback
grammar = grammar_s(users.size)
puts warning("#{users.size} user#{grammar} exposed via API: #{json_users_url}")
# Print results
table = Terminal::Table.new(headings: ['ID', 'Name', 'URL'],
rows: users)
puts table
end
end
end
end

View File

@@ -0,0 +1,73 @@
# encoding: UTF-8
class WpTarget < WebSite
module WpRSS
# Checks to see if there is an rss feed
# Will try to find the rss url in the homepage
# Only the first one found is returned
#
# This file comes by default in a WordPress installation
#
# @return [ Boolean ]
def rss_url
homepage_body = Browser.get(@uri.to_s).body
# Format: <link rel="alternate" type="application/rss+xml" title=".*" href=".*" />
homepage_body[%r{<link\s*.*\s*type=['|"]application\/rss\+xml['|"]\s*.*\stitle=".*" href=['|"]([^"]+)['|"]\s*\/?>}i, 1]
end
# Gets all the authors from the RSS feed
#
# @return [ string ]
def rss_authors(url)
# Variables
users = []
# Make the request
response = Browser.get(url, followlocation: true)
# Valid repose to view? HTTP 200?
return false unless response.code == 200
# Get output
data = response.body
# If there is nothing there, return false
return false if data.empty?
begin
# Read in RSS/XML
xml = Nokogiri::XML(data)
rescue
puts critical("Missformed XML")
return false
end
begin
# Look for <dc:creator> item
xml.xpath('//item/dc:creator').each do |node|
#Format: <dc:creator><![CDATA[.*]]></dc:creator>
users << [%r{.*}i.match(node).to_s]
end
rescue
puts critical("Missing Author field. Maybe non-standard WordPress RSS feed?")
return false
end
# Sort and uniq
users = users.sort_by { |user| user.to_s.downcase }.uniq
if users and users.size >= 1
# Feedback
grammar = grammar_s(users.size)
puts warning("Detected #{users.size} user#{grammar} from RSS feed:")
# Print results
table = Terminal::Table.new(headings: ['Name'],
rows: users)
puts table
end
end
end
end

View File

@@ -28,9 +28,12 @@ def usage
puts '-Enumerate installed themes ...' puts '-Enumerate installed themes ...'
puts "ruby #{script_name} --url www.example.com --enumerate t" puts "ruby #{script_name} --url www.example.com --enumerate t"
puts puts
puts '-Enumerate users ...' puts '-Enumerate users (from 1 - 10)...'
puts "ruby #{script_name} --url www.example.com --enumerate u" puts "ruby #{script_name} --url www.example.com --enumerate u"
puts puts
puts '-Enumerate users (from 1 - 20)...'
puts "ruby #{script_name} --url www.example.com --enumerate u[1-20]"
puts
puts '-Enumerate installed timthumbs ...' puts '-Enumerate installed timthumbs ...'
puts "ruby #{script_name} --url www.example.com --enumerate tt" puts "ruby #{script_name} --url www.example.com --enumerate tt"
puts puts
@@ -46,7 +49,7 @@ def usage
puts '-Use custom plugins directory ...' puts '-Use custom plugins directory ...'
puts "ruby #{script_name} -u www.example.com --wp-plugins-dir wp-content/custom-plugins" puts "ruby #{script_name} -u www.example.com --wp-plugins-dir wp-content/custom-plugins"
puts puts
puts '-Update the DB ...' puts '-Update the Database ...'
puts "ruby #{script_name} --update" puts "ruby #{script_name} --update"
puts puts
puts '-Debug output ...' puts '-Debug output ...'
@@ -110,7 +113,6 @@ def help
puts '--request-timeout <request-timeout> Request Timeout.' puts '--request-timeout <request-timeout> Request Timeout.'
puts '--connect-timeout <connect-timeout> Connect Timeout.' puts '--connect-timeout <connect-timeout> Connect Timeout.'
puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests.' puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests.'
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 '--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.'
@@ -118,6 +120,58 @@ def help
puts puts
end end
def clean_uri(entries)
# Extract elements
entries.flatten!
# Remove any leading/trailing spaces
entries.collect{|x| x.strip || x }
# End Of Line issues
entries.collect{|x| x.chomp! || x }
# Remove nil's
entries.compact
# Unique values only
entries.uniq!
return entries
end
# Return the full URL
def full_uri(entries)
return_object = []
# Each value now, try and make it a full URL
entries.each do |d|
begin
temp = @uri.clone
temp.path = d.strip
rescue URI::Error
temp = d.strip
end
return_object << temp.to_s
end
return return_object
end
# Parse humans.txt
# @return [ Array ] URLs generated from humans.txt
def parse_txt(url)
return_object = []
response = Browser.get(url.to_s)
body = response.body
# Get all non-comments
entries = body.split(/\n/)
# Did we get something?
if entries
# Remove any rubbish
entries = clean_uri(entries)
end
return return_object
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 30 requests with status = 0 # The target is considered down after 30 requests with status = 0
@@ -136,3 +190,4 @@ Typhoeus.on_complete do |response|
sleep(Browser.instance.throttle) sleep(Browser.instance.throttle)
end end

View File

@@ -277,7 +277,6 @@ class WpscanOptions
['--cache-ttl', GetoptLong::REQUIRED_ARGUMENT], ['--cache-ttl', GetoptLong::REQUIRED_ARGUMENT],
['--request-timeout', GetoptLong::REQUIRED_ARGUMENT], ['--request-timeout', GetoptLong::REQUIRED_ARGUMENT],
['--connect-timeout', GetoptLong::REQUIRED_ARGUMENT], ['--connect-timeout', GetoptLong::REQUIRED_ARGUMENT],
['--max-threads', GetoptLong::REQUIRED_ARGUMENT],
['--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],

View File

@@ -168,7 +168,7 @@ describe Browser do
it 'sets the proxy_auth' do it 'sets the proxy_auth' do
browser.proxy = proxy browser.proxy = proxy
browser.proxy_auth = 'user:pass' browser.proxy_auth = 'user:pass'
@expected = proxy_expectation.merge(proxyauth: 'user:pass') @expected = proxy_expectation.merge(proxyuserpwd: 'user:pass')
end end
end end
end end

View File

@@ -45,12 +45,21 @@ describe 'WpTheme::Findable' do
# FIXME: the style_url should be checked in WpTheme for absolute / relative # FIXME: the style_url should be checked in WpTheme for absolute / relative
context 'when relative url is used' do context 'when relative url is used' do
context 'when leading slash' do
it 'returns the WpTheme' do it 'returns the WpTheme' do
@file = 'relative_urls.html' @file = 'relative_urls.html'
@expected = WpTheme.new(uri, name: 'theme_name') @expected = WpTheme.new(uri, name: 'theme_name')
end end
end end
context 'when no leading slash' do
it 'returns the WpTheme' do
@file = 'relative_urls_missing_slash.html'
@expected = WpTheme.new(uri, name: 'theme_name')
end
end
end
context 'when other style.css is referenced' do context 'when other style.css is referenced' do
it 'returns the WpTheme' do it 'returns the WpTheme' do
@file = 'yootheme.html' @file = 'yootheme.html'

View File

@@ -134,6 +134,13 @@ describe 'WpVersion::Findable' do
@fixture = '/3.3.2.html' @fixture = '/3.3.2.html'
@expected = '3.3.2' @expected = '3.3.2'
end end
context 'when version >= 4.7' do
it 'returns nil' do
@fixture = '/4.7.2.html'
@expected = nil
end
end
end end
describe '::find_from_links_opml' do describe '::find_from_links_opml' do

View File

@@ -207,18 +207,6 @@ describe 'WebSite' do
end end
end end
describe '#rss_url' do
it 'returns nil if the url is not found' do
stub_request(:get, web_site.url).to_return(body: 'No RSS link in this body !')
expect(web_site.rss_url).to be_nil
end
it "returns 'http://lamp-wp/wordpress-3.5/?feed=rss2'" do
stub_request_to_fixture(url: web_site.url, fixture: fixtures_dir + '/rss_url/wordpress-3.5.htm')
expect(web_site.rss_url).to be === 'http://lamp-wp/wordpress-3.5/?feed=rss2'
end
end
describe '::has_log?' do describe '::has_log?' do
let(:log_url) { web_site.uri.merge('log.txt').to_s } let(:log_url) { web_site.uri.merge('log.txt').to_s }
let(:pattern) { %r{PHP Fatal error} } let(:pattern) { %r{PHP Fatal error} }

View File

@@ -0,0 +1 @@
<html><head><title>Test</title><link rel="stylesheet" type="text/css" href="wp-content/themes/theme_name/style.css" /></head><body></body></html>

View File

@@ -0,0 +1,99 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>WordPress &#8250; ReadMe</title>
<link rel="stylesheet" href="wp-admin/css/install.css?ver=20100228" type="text/css" />
</head>
<body>
<h1 id="logo">
<a href="https://wordpress.org/"><img alt="WordPress" src="wp-admin/images/wordpress-logo.png" /></a>
<br /> Version 4.7
</h1>
<p style="text-align: center">Semantic Personal Publishing Platform</p>
<h2>First Things First</h2>
<p>Welcome. WordPress is a very special project to me. Every developer and contributor adds something unique to the mix, and together we create something beautiful that I&#8217;m proud to be a part of. Thousands of hours have gone into WordPress, and we&#8217;re dedicated to making it better every day. Thank you for making it part of your world.</p>
<p style="text-align: right">&#8212; Matt Mullenweg</p>
<h2>Installation: Famous 5-minute install</h2>
<ol>
<li>Unzip the package in an empty directory and upload everything.</li>
<li>Open <span class="file"><a href="wp-admin/install.php">wp-admin/install.php</a></span> in your browser. It will take you through the process to set up a <code>wp-config.php</code> file with your database connection details.
<ol>
<li>If for some reason this doesn&#8217;t work, don&#8217;t worry. It doesn&#8217;t work on all web hosts. Open up <code>wp-config-sample.php</code> with a text editor like WordPad or similar and fill in your database connection details.</li>
<li>Save the file as <code>wp-config.php</code> and upload it.</li>
<li>Open <span class="file"><a href="wp-admin/install.php">wp-admin/install.php</a></span> in your browser.</li>
</ol>
</li>
<li>Once the configuration file is set up, the installer will set up the tables needed for your blog. If there is an error, double check your <code>wp-config.php</code> file, and try again. If it fails again, please go to the <a href="https://wordpress.org/support/" title="WordPress support">support forums</a> with as much data as you can gather.</li>
<li><strong>If you did not enter a password, note the password given to you.</strong> If you did not provide a username, it will be <code>admin</code>.</li>
<li>The installer should then send you to the <a href="wp-login.php">login page</a>. Sign in with the username and password you chose during the installation. If a password was generated for you, you can then click on &#8220;Profile&#8221; to change the password.</li>
</ol>
<h2>Updating</h2>
<h3>Using the Automatic Updater</h3>
<p>If you are updating from version 2.7 or higher, you can use the automatic updater:</p>
<ol>
<li>Open <span class="file"><a href="wp-admin/update-core.php">wp-admin/update-core.php</a></span> in your browser and follow the instructions.</li>
<li>You wanted more, perhaps? That&#8217;s it!</li>
</ol>
<h3>Updating Manually</h3>
<ol>
<li>Before you update anything, make sure you have backup copies of any files you may have modified such as <code>index.php</code>.</li>
<li>Delete your old WordPress files, saving ones you&#8217;ve modified.</li>
<li>Upload the new files.</li>
<li>Point your browser to <span class="file"><a href="wp-admin/upgrade.php">/wp-admin/upgrade.php</a>.</span></li>
</ol>
<h2>Migrating from other systems</h2>
<p>WordPress can <a href="https://codex.wordpress.org/Importing_Content">import from a number of systems</a>. First you need to get WordPress installed and working as described above, before using <a href="wp-admin/import.php" title="Import to WordPress">our import tools</a>.</p>
<h2>System Requirements</h2>
<ul>
<li><a href="https://secure.php.net/">PHP</a> version <strong>5.2.4</strong> or higher.</li>
<li><a href="https://www.mysql.com/">MySQL</a> version <strong>5.0</strong> or higher.</li>
</ul>
<h3>Recommendations</h3>
<ul>
<li><a href="https://secure.php.net/">PHP</a> version <strong>7</strong> or higher.</li>
<li><a href="https://www.mysql.com/">MySQL</a> version <strong>5.6</strong> or higher.</li>
<li>The <a href="https://httpd.apache.org/docs/2.2/mod/mod_rewrite.html">mod_rewrite</a> Apache module.</li>
<li><a href="https://wordpress.org/news/2016/12/moving-toward-ssl/">HTTPS</a> support.</li>
<li>A link to <a href="https://wordpress.org/">wordpress.org</a> on your site.</li>
</ul>
<h2>Online Resources</h2>
<p>If you have any questions that aren&#8217;t addressed in this document, please take advantage of WordPress&#8217; numerous online resources:</p>
<dl>
<dt><a href="https://codex.wordpress.org/">The WordPress Codex</a></dt>
<dd>The Codex is the encyclopedia of all things WordPress. It is the most comprehensive source of information for WordPress available.</dd>
<dt><a href="https://wordpress.org/news/">The WordPress Blog</a></dt>
<dd>This is where you&#8217;ll find the latest updates and news related to WordPress. Recent WordPress news appears in your administrative dashboard by default.</dd>
<dt><a href="https://planet.wordpress.org/">WordPress Planet</a></dt>
<dd>The WordPress Planet is a news aggregator that brings together posts from WordPress blogs around the web.</dd>
<dt><a href="https://wordpress.org/support/">WordPress Support Forums</a></dt>
<dd>If you&#8217;ve looked everywhere and still can&#8217;t find an answer, the support forums are very active and have a large community ready to help. To help them help you be sure to use a descriptive thread title and describe your question in as much detail as possible.</dd>
<dt><a href="https://codex.wordpress.org/IRC">WordPress <abbr title="Internet Relay Chat">IRC</abbr> Channel</a></dt>
<dd>There is an online chat channel that is used for discussion among people who use WordPress and occasionally support topics. The above wiki page should point you in the right direction. (<a href="irc://irc.freenode.net/wordpress">irc.freenode.net #wordpress</a>)</dd>
</dl>
<h2>Final Notes</h2>
<ul>
<li>If you have any suggestions, ideas, or comments, or if you (gasp!) found a bug, join us in the <a href="https://wordpress.org/support/">Support Forums</a>.</li>
<li>WordPress has a robust plugin <abbr title="application programming interface">API</abbr> that makes extending the code easy. If you are a developer interested in utilizing this, see the <a href="https://developer.wordpress.org/plugins/">Plugin Developer Handbook</a>. You shouldn&#8217;t modify any of the core code.</li>
</ul>
<h2>Share the Love</h2>
<p>WordPress has no multi-million dollar marketing campaign or celebrity sponsors, but we do have something even better&#8212;you. If you enjoy WordPress please consider telling a friend, setting it up for someone less knowledgable than yourself, or writing the author of a media article that overlooks us.</p>
<p>WordPress is the official continuation of <a href="http://cafelog.com/">b2/caf&#233;log</a>, which came from Michel V. The work has been continued by the <a href="https://wordpress.org/about/">WordPress developers</a>. If you would like to support WordPress, please consider <a href="https://wordpress.org/donate/" title="Donate to WordPress">donating</a>.</p>
<h2>License</h2>
<p>WordPress is free software, and is released under the terms of the <abbr title="GNU General Public License">GPL</abbr> version 2 or (at your option) any later version. See <a href="license.txt">license.txt</a>.</p>
</body>
</html>

View File

@@ -0,0 +1,108 @@
# encoding: UTF-8
shared_examples 'WebSite::HumansTxt' do
let(:known_dirs) { WebSite::HumansTxt.known_dirs }
describe '#humans_url' do
it 'returns the correct url' do
expect(web_site.humans_url).to eql 'http://example.localhost/humans.txt'
end
end
describe '#has_humans?' do
it 'returns true' do
stub_request(:get, web_site.humans_url).to_return(status: 200)
expect(web_site.has_humans?).to be_truthy
end
it 'returns false' do
stub_request(:get, web_site.humans_url).to_return(status: 404)
expect(web_site.has_humans?).to be_falsey
end
end
describe '#parse_humans_txt' do
context 'installed in root' do
after :each do
stub_request_to_fixture(url: web_site.humans_url, fixture: @fixture)
humans = web_site.parse_humans_txt
expect(humans).to match_array @expected
end
it 'returns an empty Array (empty humans.txt)' do
@fixture = fixtures_dir + '/humans_txt/empty_humans.txt'
@expected = []
end
it 'returns an empty Array (invalid humans.txt)' do
@fixture = fixtures_dir + '/humans_txt/invalid_humans.txt'
@expected = []
end
it 'returns some urls and some strings' do
@fixture = fixtures_dir + '/humans_txt/invalid_humans_2.txt'
@expected = %w(
/ÖÜ()=?
http://10.0.0.0/wp-includes/
http://example.localhost/asdf/
wooooza
)
end
it 'returns an Array of urls (valid humans.txt)' do
@fixture = fixtures_dir + '/humans_txt/humans.txt'
@expected = %w(
http://example.localhost/wordpress/admin/
http://example.localhost/wordpress/wp-admin/
http://example.localhost/wordpress/secret/
http://example.localhost/Wordpress/wp-admin/
http://example.localhost/wp-admin/tralling-space/
http://example.localhost/asdf/
)
end
it 'removes duplicate entries from humans.txt test 1' do
@fixture = fixtures_dir + '/humans_txt/humans_duplicate_1.txt'
@expected = %w(
http://example.localhost/wordpress/
http://example.localhost/wordpress/admin/
http://example.localhost/wordpress/wp-admin/
http://example.localhost/wordpress/secret/
http://example.localhost/Wordpress/wp-admin/
http://example.localhost/wp-admin/tralling-space/
http://example.localhost/asdf/
)
end
it 'removes duplicate entries from humans.txt test 2' do
@fixture = fixtures_dir + '/humans_txt/humans_duplicate_2.txt'
@expected = nil
end
end
context 'installed in sub directory' do
it 'returns an Array of urls (valid humans.txt, WP installed in subdir)' do
web_site_sub = WebSite.new('http://example.localhost/wordpress/')
fixture = fixtures_dir + '/humans_txt/humans.txt'
expected = %w(
http://example.localhost/wordpress/admin/
http://example.localhost/wordpress/secret/
http://example.localhost/Wordpress/wp-admin/
http://example.localhost/wp-admin/tralling-space/
http://example.localhost/asdf/
)
stub_request_to_fixture(url: web_site_sub.humans_url, fixture: fixture)
humans = web_site_sub.parse_humans_txt
expect(humans).to match_array expected
end
end
end
describe '#known_dirs' do
it 'does not contain duplicates' do
expect(known_dirs.flatten.uniq.length).to eq known_dirs.length
end
end
end

View File

@@ -0,0 +1,108 @@
# encoding: UTF-8
shared_examples 'WebSite::SecurityTxt' do
let(:known_dirs) { WebSite::SecurityTxt.known_dirs }
describe '#security_url' do
it 'returns the correct url' do
expect(web_site.security_url).to eql 'http://example.localhost/security.txt'
end
end
describe '#has_security?' do
it 'returns true' do
stub_request(:get, web_site.security_url).to_return(status: 200)
expect(web_site.has_security?).to be_truthy
end
it 'returns false' do
stub_request(:get, web_site.security_url).to_return(status: 404)
expect(web_site.has_security?).to be_falsey
end
end
describe '#parse_security_txt' do
context 'installed in root' do
after :each do
stub_request_to_fixture(url: web_site.security_url, fixture: @fixture)
security = web_site.parse_security_txt
expect(security).to match_array @expected
end
it 'returns an empty Array (empty security.txt)' do
@fixture = fixtures_dir + '/security_txt/empty_security.txt'
@expected = []
end
it 'returns an empty Array (invalid security.txt)' do
@fixture = fixtures_dir + '/security_txt/invalid_security.txt'
@expected = []
end
it 'returns some urls and some strings' do
@fixture = fixtures_dir + '/security_txt/invalid_security_2.txt'
@expected = %w(
/ÖÜ()=?
http://10.0.0.0/wp-includes/
http://example.localhost/asdf/
wooooza
)
end
it 'returns an Array of urls (valid security.txt)' do
@fixture = fixtures_dir + '/security_txt/security.txt'
@expected = %w(
http://example.localhost/wordpress/admin/
http://example.localhost/wordpress/wp-admin/
http://example.localhost/wordpress/secret/
http://example.localhost/Wordpress/wp-admin/
http://example.localhost/wp-admin/tralling-space/
http://example.localhost/asdf/
)
end
it 'removes duplicate entries from security.txt test 1' do
@fixture = fixtures_dir + '/security_txt/security_duplicate_1.txt'
@expected = %w(
http://example.localhost/wordpress/
http://example.localhost/wordpress/admin/
http://example.localhost/wordpress/wp-admin/
http://example.localhost/wordpress/secret/
http://example.localhost/Wordpress/wp-admin/
http://example.localhost/wp-admin/tralling-space/
http://example.localhost/asdf/
)
end
it 'removes duplicate entries from security.txt test 2' do
@fixture = fixtures_dir + '/security_txt/security_duplicate_2.txt'
@expected = nil
end
end
context 'installed in sub directory' do
it 'returns an Array of urls (valid security.txt, WP installed in subdir)' do
web_site_sub = WebSite.new('http://example.localhost/wordpress/')
fixture = fixtures_dir + '/security_txt/security.txt'
expected = %w(
http://example.localhost/wordpress/admin/
http://example.localhost/wordpress/secret/
http://example.localhost/Wordpress/wp-admin/
http://example.localhost/wp-admin/tralling-space/
http://example.localhost/asdf/
)
stub_request_to_fixture(url: web_site_sub.security_url, fixture: fixture)
security = web_site_sub.parse_security_txt
expect(security).to match_array expected
end
end
end
describe '#known_dirs' do
it 'does not contain duplicates' do
expect(known_dirs.flatten.uniq.length).to eq known_dirs.length
end
end
end

View File

@@ -16,7 +16,7 @@ shared_examples 'WpItem::Infos' do
end end
context 'when the file exists' do context 'when the file exists' do
%w{readme.txt README.TXT}.each do |readme| %w{readme.txt readme.md}.each do |readme|
it 'returns the correct url' do it 'returns the correct url' do
url = uri.merge(readme).to_s url = uri.merge(readme).to_s
@expected = url @expected = url
@@ -48,25 +48,42 @@ shared_examples 'WpItem::Infos' do
end end
describe '#changelog_url' do describe '#changelog_url' do
after { expect(subject.changelog_url).to eql @expected }
it 'returns nil' do
stub_request(:get, /.*/).to_return(status: 404)
@expected = nil
end
context 'when the file exists' do
%w{changelog.txt CHANGELOG.md}.each do |changelog|
it 'returns the correct url' do it 'returns the correct url' do
expect(subject.changelog_url).to eq changelog_url url = uri.merge(changelog).to_s
@expected = url
stub_request(:get, %r{^(?!#{url})}).to_return(status: 404)
stub_request(:get, url).to_return(status: 200)
end
end
end end
end end
describe '#has_changelog?' do describe '#has_changelog?' do
after :each do after do
stub_request(:get, subject.changelog_url).to_return(status: @status) allow(subject).to receive_messages(changelog_url: @stub)
expect(subject.has_changelog?).to eql @expected expect(subject.has_changelog?).to eql @expected
end end
it 'returns true on a 200' do context 'when changelog_url is nil'
@status = 200 it 'returns false' do
@expected = true @stub = nil
@expected = false
end end
it 'returns false otherwise' do context 'when changelog_url is not nil'
@status = 404 it 'returns true' do
@expected = false @stub = uri.merge('changelog.txt').to_s
@expected = true
end end
end end

View File

@@ -13,7 +13,7 @@ shared_examples 'WpItem::Vulnerable' do
let(:empty_file) { MODELS_FIXTURES + '/wp_item/vulnerable/empty.json' } let(:empty_file) { MODELS_FIXTURES + '/wp_item/vulnerable/empty.json' }
before do before do
stub_request(:get, /.*\/readme\.txt/i) stub_request(:get, /.*\/readme\.(?:txt|md)/i)
stub_request(:get, /.*\/style\.css/i) stub_request(:get, /.*\/style\.css/i)
end end
@@ -94,7 +94,7 @@ shared_examples 'WpItem::Vulnerable' do
context 'no version found in wp_item' do context 'no version found in wp_item' do
before do before do
stub_request(:get, /.*\/readme\.txt/i).to_return(status: 404) stub_request(:get, /.*\/readme\.(?:txt|md)/i).to_return(status: 404)
stub_request(:get, /.*\/style\.css/i).to_return(status: 404) stub_request(:get, /.*\/style\.css/i).to_return(status: 404)
end end

238
wpscan.rb
View File

@@ -10,6 +10,7 @@ require File.join(__dir__, 'lib', 'wpscan', 'wpscan_helper')
def main def main
begin begin
wpscan_options = WpscanOptions.load_from_arguments wpscan_options = WpscanOptions.load_from_arguments
date = last_update
$log = wpscan_options.log $log = wpscan_options.log
@@ -27,7 +28,7 @@ def main
# check if file exists and has a size greater zero # check if file exists and has a size greater zero
if File.exist?($log) && File.size?($log) if File.exist?($log) && File.size?($log)
puts notice("The supplied log file #{$log} already exists. If you continue the new output will be appended.") puts notice("The supplied log file #{$log} already exists. If you continue the new output will be appended.")
print '[?] Do you want to continue? [Y]es [N]o, default: [N]' print '[?] Do you want to continue? [Y]es [N]o, default: [N] >'
if Readline.readline !~ /^y/i if Readline.readline !~ /^y/i
# unset logging so puts will try to log to the file # unset logging so puts will try to log to the file
$log = nil $log = nil
@@ -54,6 +55,8 @@ def main
unless wpscan_options.has_options? unless wpscan_options.has_options?
# first parameter only url? # first parameter only url?
if ARGV.length == 1 if ARGV.length == 1
puts
puts notice("Please use '-u #{ARGV[0]}' next time")
wpscan_options.url = ARGV[0] wpscan_options.url = ARGV[0]
else else
usage() usage()
@@ -72,8 +75,7 @@ def main
if wpscan_options.version if wpscan_options.version
puts "Current version: #{WPSCAN_VERSION}" puts "Current version: #{WPSCAN_VERSION}"
date = last_update puts "Last database update: #{date.strftime('%Y-%m-%d')}" unless date.nil?
puts "Last DB update: #{date.strftime('%Y-%m-%d')}" unless date.nil?
exit(0) exit(0)
end end
@@ -83,28 +85,44 @@ def main
wpscan_options.to_h.merge(max_threads: wpscan_options.threads) wpscan_options.to_h.merge(max_threads: wpscan_options.threads)
) )
# check if db file needs upgrade and we are not running in batch mode # Check if database needs upgrade (if its older than 5 days) and we are not running in --batch mode
# also no need to check if the user supplied the --update switch # Also no need to check if the user supplied the --update switch
if update_required? && !wpscan_options.batch && !wpscan_options.update if update_required? and not wpscan_options.batch and not wpscan_options.update
puts notice('It seems like you have not updated the database for some time.') # Banner
print '[?] Do you want to update now? [Y]es [N]o [A]bort, default: [N]' puts
if (input = Readline.readline) =~ /^y/i puts notice('It seems like you have not updated the database for some time')
puts notice("Last database update: #{date.strftime('%Y-%m-%d')}") unless date.nil?
# User prompt
print '[?] Do you want to update now? [Y]es [N]o [A]bort update, default: [N] > '
if (input = Readline.readline) =~ /^a/i
puts 'Update aborted'
elsif input =~ /^y/i
wpscan_options.update = true wpscan_options.update = true
elsif input =~ /^a/i end
puts 'Scan aborted'
exit(1) # Is there a database to go on with?
if missing_db_files? and not wpscan_options.update
# Check for data.zip
if has_db_zip?
puts notice('Extracting the Database ...')
# Extract data.zip
extract_db_zip
puts notice('Extraction completed')
# Missing, so can't go on!
else else
if missing_db_file? puts critical('You can not run a scan without any databases')
puts critical('You can not run a scan without any databases. Extract the data.zip file.')
exit(1) exit(1)
end end
end end
end end
# Should we update?
if wpscan_options.update if wpscan_options.update
puts notice('Updating the Database ...') puts notice('Updating the Database ...')
DbUpdater.new(DATA_DIR).update(wpscan_options.verbose) DbUpdater.new(DATA_DIR).update(wpscan_options.verbose)
puts notice('Update completed.') puts notice('Update completed')
# Exit program if only option --update is used # Exit program if only option --update is used
exit(0) unless wpscan_options.url exit(0) unless wpscan_options.url
end end
@@ -120,12 +138,18 @@ def main
end end
if wp_target.ssl_error? if wp_target.ssl_error?
raise "The target site returned an SSL/TLS error. You can try again using the --disable-tls-checks option.\nError: #{wp_target.get_root_path_return_code}\nSee here for a detailed explanation of the error: http://www.rubydoc.info/github/typhoeus/ethon/Ethon/Easy:return_code" raise "The target site returned an SSL/TLS error. You can try again using --disable-tls-checks\nError: #{wp_target.get_root_path_return_code}\nSee here for a detailed explanation of the error: http://www.rubydoc.info/github/typhoeus/ethon/Ethon/Easy:return_code"
end end
# Remote website up? # Remote website up?
unless wp_target.online? unless wp_target.online?
raise "The WordPress URL supplied '#{wp_target.uri}' seems to be down. Maybe the site is blocking wpscan so you can try the --random-agent parameter." if wpscan_options.user_agent
puts info("User-Agent: #{wpscan_options.user_agent}")
raise "The WordPress URL supplied '#{wp_target.uri}' seems to be down. Maybe the site is blocking the user-agent?"
else
raise "The WordPress URL supplied '#{wp_target.uri}' seems to be down. Maybe the site is blocking the wpscan user-agent, so you can try --random-agent"
end
end end
if wpscan_options.proxy if wpscan_options.proxy
@@ -145,7 +169,7 @@ def main
puts "Following redirection #{redirection}" puts "Following redirection #{redirection}"
else else
puts notice("The remote host tried to redirect to: #{redirection}") puts notice("The remote host tried to redirect to: #{redirection}")
print '[?] Do you want follow the redirection ? [Y]es [N]o [A]bort, default: [N]' print '[?] Do you want follow the redirection ? [Y]es [N]o [A]bort, default: [N] >'
end end
if wpscan_options.follow_redirection || !wpscan_options.batch if wpscan_options.follow_redirection || !wpscan_options.batch
if wpscan_options.follow_redirection || (input = Readline.readline) =~ /^y/i if wpscan_options.follow_redirection || (input = Readline.readline) =~ /^y/i
@@ -174,7 +198,7 @@ def main
# Remote website is wordpress? # Remote website is wordpress?
unless wpscan_options.force unless wpscan_options.force
unless wp_target.wordpress? unless wp_target.wordpress?
raise 'The remote website is up, but does not seem to be running WordPress.' raise 'The remote website is up, but does not seem to be running WordPress. If you are sure, use --force'
end end
end end
@@ -196,40 +220,9 @@ def main
start_memory = get_memory_usage unless windows? start_memory = get_memory_usage unless windows?
puts info("URL: #{wp_target.url}") puts info("URL: #{wp_target.url}")
puts info("Started: #{start_time.asctime}") puts info("Started: #{start_time.asctime}")
puts info("User-Agent: #{wpscan_options.user_agent}") if wpscan_options.verbose and wpscan_options.user_agent
puts puts
if wp_target.has_robots?
puts info("robots.txt available under: '#{wp_target.robots_url}'")
wp_target.parse_robots_txt.each do |dir|
puts info("Interesting entry from robots.txt: #{dir}")
end
end
if wp_target.has_readme?
puts warning("The WordPress '#{wp_target.readme_url}' file exists exposing a version number")
end
if wp_target.has_full_path_disclosure?
puts warning("Full Path Disclosure (FPD) in '#{wp_target.full_path_disclosure_url}': #{wp_target.full_path_disclosure_data}")
end
if wp_target.has_debug_log?
puts critical("Debug log file found: #{wp_target.debug_log_url}")
end
wp_target.config_backup.each do |file_url|
puts critical("A wp-config.php backup file has been found in: '#{file_url}'")
end
if wp_target.search_replace_db_2_exists?
puts critical("searchreplacedb2.php has been found in: '#{wp_target.search_replace_db_2_url}'")
end
if wp_target.emergency_exists?
puts critical("emergency.php has been found in: '#{wp_target.emergency_url}'")
end
wp_target.interesting_headers.each do |header| wp_target.interesting_headers.each do |header|
output = info('Interesting header: ') output = info('Interesting header: ')
@@ -242,6 +235,66 @@ def main
end end
end end
if wp_target.has_robots?
code = get_http_status(wp_target.robots_url)
puts info("robots.txt available under: #{wp_target.robots_url} [HTTP #{code}]")
wp_target.parse_robots_txt.each do |dir|
code = get_http_status(dir)
puts info("Interesting entry from robots.txt: #{dir} [HTTP #{code}]")
end
end
if wp_target.has_sitemap?
code = get_http_status(wp_target.sitemap_url)
puts info("Sitemap found: #{wp_target.sitemap_url} [HTTP #{code}]")
wp_target.parse_sitemap.each do |dir|
code = get_http_status(dir)
puts info("Sitemap entry: #{dir} [HTTP #{code}]")
end
end
code = get_http_status(wp_target.humans_url)
if code == 200
puts info("humans.txt available under: #{wp_target.humans_url} [HTTP #{code}]")
parse_txt(wp_target.humans_url).each do |dir|
puts info("Entry from humans.txt: #{dir}")
end
end
code = get_http_status(wp_target.security_url)
if code == 200
puts info("security.txt available under: #{wp_target.security_url} [HTTP #{code}]")
parse_txt(wp_target.security_url).each do |dir|
puts info("Entry from security.txt: #{dir}")
end
end
unless wp_target.sql_file_export.empty?
wp_target.sql_file_export.each do |file|
puts critical("SQL export file found: #{file}")
end
end
if wp_target.has_debug_log?
puts critical("Debug log file found: #{wp_target.debug_log_url}")
end
wp_target.config_backup.each do |file_url|
puts critical("A wp-config.php backup file has been found in: #{file_url}")
end
if wp_target.search_replace_db_2_exists?
puts critical("searchreplacedb2.php has been found in: #{wp_target.search_replace_db_2_url}")
end
if wp_target.emergency_exists?
puts critical("emergency.php has been found in: #{wp_target.emergency_url}")
end
if wp_target.multisite? if wp_target.multisite?
puts info('This site seems to be a multisite (http://codex.wordpress.org/Glossary#Multisite)') puts info('This site seems to be a multisite (http://codex.wordpress.org/Glossary#Multisite)')
end end
@@ -250,12 +303,35 @@ def main
puts info("This site has 'Must Use Plugins' (http://codex.wordpress.org/Must_Use_Plugins)") puts info("This site has 'Must Use Plugins' (http://codex.wordpress.org/Must_Use_Plugins)")
end end
if wp_target.registration_enabled? if wp_target.has_xml_rpc?
puts warning("Registration is enabled: #{wp_target.registration_url}") code = get_http_status(wp_target.xml_rpc_url)
puts info("XML-RPC Interface available under: #{wp_target.xml_rpc_url} [HTTP #{code}]")
end end
if wp_target.has_xml_rpc? # Test to see if MAIN API URL gives anything back
puts info("XML-RPC Interface available under: #{wp_target.xml_rpc_url}") if wp_target.has_api?(wp_target.json_url)
code = get_http_status(wp_target.json_url)
puts info("API exposed: #{wp_target.json_url} [HTTP #{code}]")
# Test to see if USER API URL gives anything back
if wp_target.has_api?(wp_target.json_users_url)
# Print users from JSON
wp_target.json_get_users(wp_target.json_users_url)
end
end
# Get RSS
rss = wp_target.rss_url
if rss
code = get_http_status(rss)
puts info("Found an RSS Feed: #{rss} [HTTP #{code}]")
# Print users from RSS feed
wp_target.rss_authors(rss)
end
if wp_target.has_full_path_disclosure?
puts warning("Full Path Disclosure (FPD) in '#{wp_target.full_path_disclosure_url}': #{wp_target.full_path_disclosure_data}")
end end
if wp_target.upload_directory_listing_enabled? if wp_target.upload_directory_listing_enabled?
@@ -271,13 +347,20 @@ def main
exclude_content: wpscan_options.exclude_content_based exclude_content: wpscan_options.exclude_content_based
} }
if wp_version = wp_target.version(WP_VERSIONS_FILE) puts
puts info('Enumerating WordPress version ...')
if (wp_version = wp_target.version(WP_VERSIONS_FILE))
if wp_target.has_readme? && VersionCompare::lesser?(wp_version.identifier, '4.7')
puts warning("The WordPress '#{wp_target.readme_url}' file exists exposing a version number")
end
wp_version.output(wpscan_options.verbose) wp_version.output(wpscan_options.verbose)
else else
puts puts
puts notice('WordPress version can not be detected') puts notice('WordPress version can not be detected')
end end
if wp_theme = wp_target.theme if wp_theme = wp_target.theme
puts puts
# Theme version is handled in #to_s # Theme version is handled in #to_s
@@ -304,14 +387,11 @@ def main
wp_plugins = WpPlugins.passive_detection(wp_target) wp_plugins = WpPlugins.passive_detection(wp_target)
if !wp_plugins.empty? if !wp_plugins.empty?
if wp_plugins.size == 1 grammar = grammar_s(wp_plugins.size)
puts " | #{wp_plugins.size} plugin found:" puts " | #{wp_plugins.size} plugin#{grammar} found:"
else
puts " | #{wp_plugins.size} plugins found:"
end
wp_plugins.output(wpscan_options.verbose) wp_plugins.output(wpscan_options.verbose)
else else
puts info('No plugins found') puts info('No plugins found passively')
end end
end end
@@ -343,12 +423,14 @@ def main
puts puts
if !wp_plugins.empty? if !wp_plugins.empty?
puts info("We found #{wp_plugins.size} plugins:") grammar = grammar_s(wp_plugins.size)
puts info("We found #{wp_plugins.size} plugin#{grammar}:")
wp_plugins.output(wpscan_options.verbose) wp_plugins.output(wpscan_options.verbose)
else else
puts info('No plugins found') puts info('No plugins found')
end end
end end
# Enumerate installed themes # Enumerate installed themes
@@ -378,12 +460,14 @@ def main
) )
puts puts
if !wp_themes.empty? if !wp_themes.empty?
puts info("We found #{wp_themes.size} themes:") grammar = grammar_s(wp_themes.size)
puts info("We found #{wp_themes.size} theme#{grammar}:")
wp_themes.output(wpscan_options.verbose) wp_themes.output(wpscan_options.verbose)
else else
puts info('No themes found') puts info('No themes found')
end end
end end
if wpscan_options.enumerate_timthumbs if wpscan_options.enumerate_timthumbs
@@ -393,18 +477,20 @@ def main
wp_timthumbs = WpTimthumbs.aggressive_detection(wp_target, wp_timthumbs = WpTimthumbs.aggressive_detection(wp_target,
enum_options.merge( enum_options.merge(
file: DATA_DIR + '/timthumbs.txt', file: TIMTHUMBS_FILE,
theme_name: wp_theme ? wp_theme.name : nil theme_name: wp_theme ? wp_theme.name : nil
) )
) )
puts puts
if !wp_timthumbs.empty? if !wp_timthumbs.empty?
puts info("We found #{wp_timthumbs.size} timthumb file/s:") grammar = grammar_s(wp_timthumbs.size)
puts info("We found #{wp_timthumbs.size} timthumb file#{grammar}:")
wp_timthumbs.output(wpscan_options.verbose) wp_timthumbs.output(wpscan_options.verbose)
else else
puts info('No timthumb files found') puts info('No timthumb files found')
end end
end end
# If we haven't been supplied a username/usernames list, enumerate them... # If we haven't been supplied a username/usernames list, enumerate them...
@@ -432,7 +518,8 @@ def main
exit(1) exit(1)
end end
else else
puts info("Identified the following #{wp_users.size} user/s:") grammar = grammar_s(wp_users.size)
puts info("We identified the following #{wp_users.size} user#{grammar}:")
wp_users.output(margin_left: ' ' * 4) wp_users.output(margin_left: ' ' * 4)
if wp_users[0].login == "admin" if wp_users[0].login == "admin"
puts warning("Default first WordPress username 'admin' is still used") puts warning("Default first WordPress username 'admin' is still used")
@@ -442,10 +529,12 @@ def main
else else
wp_users = WpUsers.new wp_users = WpUsers.new
# Username file?
if wpscan_options.usernames if wpscan_options.usernames
File.open(wpscan_options.usernames).each do |username| File.open(wpscan_options.usernames).each do |username|
wp_users << WpUser.new(wp_target.uri, login: username.chomp) wp_users << WpUser.new(wp_target.uri, login: username.chomp)
end end
# Single username?
else else
wp_users << WpUser.new(wp_target.uri, login: wpscan_options.username) wp_users << WpUser.new(wp_target.uri, login: wpscan_options.username)
end end
@@ -455,7 +544,6 @@ def main
bruteforce = true bruteforce = true
if wpscan_options.wordlist if wpscan_options.wordlist
if wp_target.has_login_protection? if wp_target.has_login_protection?
protection_plugin = wp_target.login_protection_plugin() protection_plugin = wp_target.login_protection_plugin()
puts puts
@@ -481,6 +569,7 @@ def main
else else
puts critical('Brute forcing aborted') puts critical('Brute forcing aborted')
end end
end end
stop_time = Time.now stop_time = Time.now
@@ -489,9 +578,9 @@ def main
puts puts
puts info("Finished: #{stop_time.asctime}") puts info("Finished: #{stop_time.asctime}")
puts info("Requests Done: #{@total_requests_done}")
puts info("Memory used: #{used_memory.bytes_to_human}") unless windows?
puts info("Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}") puts info("Elapsed time: #{Time.at(elapsed).utc.strftime('%H:%M:%S')}")
puts info("Requests made: #{@total_requests_done}")
puts info("Memory used: #{used_memory.bytes_to_human}") unless windows?
# do nothing on interrupt # do nothing on interrupt
rescue Interrupt rescue Interrupt
@@ -501,14 +590,16 @@ def main
puts critical(e.message) puts critical(e.message)
if e.file if e.file
puts critical("Current Version: #{WPSCAN_VERSION}")
puts critical('Downloaded File Content:') puts critical('Downloaded File Content:')
puts e.file[0..500] puts e.file[0..500] # print first 500 chars
puts '.........' puts '.........'
puts e.file[-500..-1] || e.file # print last 500 chars or the whole file if it's < 500
puts puts
end end
puts critical('Some hints to help you with this issue:') puts critical('Some hints to help you with this issue:')
puts critical('-) Try updating again') puts critical('-) Try updating again using --verbose')
puts critical('-) If you see SSL/TLS related error messages you have to fix your local TLS setup') puts critical('-) If you see SSL/TLS related error messages you have to fix your local TLS setup')
puts critical('-) Windows is still not supported') puts critical('-) Windows is still not supported')
exit(1) exit(1)
@@ -522,11 +613,14 @@ def main
end end
exit(1) exit(1)
ensure ensure
# Make sure there was an argument
if ARGV.length != 0
# Ensure a clean abort of Hydra # Ensure a clean abort of Hydra
# See https://github.com/wpscanteam/wpscan/issues/461#issuecomment-42735615 # See https://github.com/wpscanteam/wpscan/issues/461#issuecomment-42735615
Browser.instance.hydra.abort Browser.instance.hydra.abort
Browser.instance.hydra.run Browser.instance.hydra.run
end end
end
end end
main() main()