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
Dockerfile
## TEMP
.idea/
.yardoc/
bundle/
cache/
coverage/
git/
**/*.md
**/*.orig
*.orig
CREDITS
data.zip
DISCLAIMER.txt
example.conf.json
bin/
log.txt

31
.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
cache
coverage
.bundle
.DS_Store
.DS_Store?
*.sublime-*
.idea
.*.swp
log.txt
.yardoc
debug.log
wordlist.txt
rspec_results.html
data/
vendor/
.DS_Store?

View File

@@ -1 +1 @@
2.4.1
2.5.1

View File

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

View File

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

View File

@@ -1,11 +1,12 @@
source 'https://rubygems.org'
gem 'typhoeus', '>=1.1.2'
gem 'nokogiri', '>=1.7.0.1'
gem 'addressable', '>=2.5.0'
gem 'yajl-ruby', '>=1.3.0' # Better JSON parser regarding memory usage
gem 'terminal-table', '>=1.6.0'
gem 'nokogiri', '>=1.7.0.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
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
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.
@@ -8,7 +8,7 @@ Cases that include commercialization of WPScan require a commercial, non-free li
1.1 “License” means this document.
1.2 “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
1.3 “WPScan Team” means 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

View File

@@ -1,16 +1,17 @@
![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)
[![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/)
[![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
## 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.
@@ -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.
# 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`
## 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
```
(This mounts the host directory `~/wordlists` to the container in the path `/wordlists`)
Use logfile option
```
# 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
```
(This mounts the host directory `~/wordlists` to the container in the path `/wordlists`)
Published on https://hub.docker.com/r/wpscanteam/wpscan/
# Manual install
## 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
- RubyGems - Recommended: latest
- 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
source ~/.rvm/scripts/rvm
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
rvm install 2.4.1
rvm use 2.4.1 --default
rvm install 2.5.1
rvm use 2.5.1 --default
echo "gem: --no-ri --no-rdoc" > ~/.gemrc
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
@@ -190,39 +195,6 @@ https://rvm.io/integration/gnome-terminal#integrating-rvm-with-gnome-terminal
# 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
```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.
--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.

View File

@@ -9,4 +9,4 @@ done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
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 = {})
if @proxy
params.merge!(proxy: @proxy)
params.merge!(proxyauth: @proxy_auth) if @proxy_auth
params.merge!(proxyuserpwd: @proxy_auth) if @proxy_auth
end
if @basic_auth

View File

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

View File

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

View File

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

View File

@@ -1,36 +1,34 @@
# encoding: UTF-8
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/../"
DATA_DIR = File.join(ROOT_DIR, 'data')
CONF_DIR = File.join(ROOT_DIR, 'conf')
CACHE_DIR = File.join(ROOT_DIR, 'cache')
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan')
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')
# Location directories
LIB_DIR = File.expand_path(File.join(__dir__, '..')) # wpscan/lib/
ROOT_DIR = File.expand_path(File.join(LIB_DIR, '..')) # wpscan/ - expand_path is used to get "wpscan/" instead of "wpscan/lib/../"
USER_DIR = File.expand_path(Dir.home) # ~/
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
COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins')
WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM
# Core WPScan files
DEFAULT_LOG_FILE = File.join(USER_DIR, '.wpscan/log.txt') # ~/.wpscan/log.txt
DATA_FILE = File.join(ROOT_DIR, 'data.zip') # wpscan/data.zip
# Data files
WORDPRESSES_FILE = File.join(DATA_DIR, 'wordpresses.json')
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.json')
THEMES_FILE = File.join(DATA_DIR, 'themes.json')
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml')
LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml')
WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd')
LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd')
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update')
# WPScan Data files (data.zip)
LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update') # ~/.wpscan/data/.last_update
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.json') # ~/.wpscan/data/plugins.json
THEMES_FILE = File.join(DATA_DIR, 'themes.json') # ~/.wpscan/data/themes.json
TIMTHUMBS_FILE = File.join(DATA_DIR, 'timthumbs.txt') # ~/.wpscan/data/timthumbs.txt
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt') # ~/.wpscan/data/user-agents.txt
WORDPRESSES_FILE = File.join(DATA_DIR, 'wordpresses.json') # ~/.wpscan/data/wordpresses.json
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml') # ~/.wpscan/data/wp_versions.xml
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(WPSCAN_LIB_DIR)
@@ -50,6 +48,7 @@ def windows?
end
require 'environment'
require 'zip'
def escape_glob(s)
s.gsub(/[\\\{\}\[\]\*\?]/) { |x| '\\' + x }
@@ -78,13 +77,39 @@ def add_trailing_slash(url)
url =~ /\/$/ ? url : "#{url}/"
end
def missing_db_file?
def missing_db_files?
DbUpdater::FILES.each do |db_file|
return true unless File.exist?(File.join(DATA_DIR, db_file))
end
false
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
date = nil
if File.exists?(LAST_UPDATE_FILE)
@@ -94,6 +119,7 @@ def last_update
date
end
# Was it 5 days ago?
def update_required?
date = last_update
day_seconds = 24 * 60 * 60
@@ -161,7 +187,7 @@ def banner
puts ' WordPress Security Scanner by the WPScan Team '
puts " Version #{WPSCAN_VERSION}"
puts ' Sponsored by Sucuri - https://sucuri.net'
puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, pvdl, @_FireFart_'
puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_'
puts '_______________________________________________________________'
puts
end
@@ -253,14 +279,26 @@ end
# @return [ String ] A random user-agent from data/user-agents.txt
def get_random_user_agent
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')
# Read every line in the file
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*(#|\/\/)/
# Add to array
user_agents << line.strip
end
# Close file handler
f.close
# return ransom user-agent
# Return random user-agent
user_agents.sample
end
@@ -274,3 +312,21 @@ end
def url_encode(str)
CGI.escape(str).gsub("+", "%20")
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)
@repo_directory = repo_directory
fail "#{repo_directory} is not writable" unless \
Pathname.new(repo_directory).writable?
unless Dir.exist?(@repo_directory)
FileUtils.mkdir_p(@repo_directory)
end
unless Pathname.new(@repo_directory).writable?
fail "#{@repo_directory} is not writable"
end
end
# @return [ Hash ] The params for Typhoeus::Request
@@ -83,7 +88,7 @@ class DbUpdater
def update(verbose = false)
FILES.each do |filename|
begin
puts "[+] Checking #{filename}" if verbose
puts "[+] Checking: #{filename}" if verbose
db_checksum = remote_file_checksum(filename)
# Checking if the file needs to be updated
@@ -95,7 +100,7 @@ class DbUpdater
puts ' [i] Needs to be updated' if verbose
create_backup(filename)
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)
puts " [i] Downloaded File Checksum: #{dl_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
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
#
# @param [ String ] url
@@ -35,9 +14,34 @@ class WpItem
Browser.get(url).code == 200
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
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
# @return [ Boolean ]

View File

@@ -23,7 +23,7 @@ class WpItem
if version.nil? && vulnerabilities.length > 0
puts
puts warning('We could not determine a version so all vulnerabilities are printed out')
puts warning('We could not determine the version installed. All of the past known vulnerabilities will be output to allow you to do your own manual investigation.')
end
vulnerabilities.output

View File

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

View File

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

View File

@@ -1,11 +1,19 @@
# encoding: UTF-8
require 'web_site/robots_txt'
require 'web_site/humans_txt'
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
include WebSite::RobotsTxt
include WebSite::HumansTxt
include WebSite::InterestingHeaders
include WebSite::RobotsTxt
include WebSite::SecurityTxt
include WebSite::Sitemap
include WebSite::SqlFileExport
attr_reader :uri
@@ -121,13 +129,6 @@ class WebSite
@error_404_hash
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
# 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
# @return [ Array ] URLs generated from robots.txt
def parse_robots_txt
return unless has_robots?
return_object = []
# Make request
response = Browser.get(robots_url.to_s)
body = response.body
# Get all allow and disallow urls
entries = body.scan(/^(?:dis)?allow:\s*(.*)$/i)
# Did we get something?
if entries
entries.flatten!
entries.compact.sort!
entries.uniq!
# Remove any rubbish
entries = clean_uri(entries)
# Sort
entries.sort!
# Wordpress URL
wordpress_path = @uri.path
# Each "boring" value as defined below, remove
RobotsTxt.known_dirs.each do |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(/\/+/, '/')
entries.delete(dir_with_subdir)
end
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
# Convert to full URIs
return_object = full_uri(entries)
end
return_object
return return_object
end
protected
# Useful ~ "function do_robots()" -> https://github.com/WordPress/WordPress/blob/master/wp-includes/functions.php
#
# @return [ Array ]
def self.known_dirs
%w{
/
/wp-admin/
/wp-admin/admin-ajax.php
/wp-includes/
/wp-content/
}
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
require 'web_site'
require 'wp_target/wp_readme'
require 'wp_target/wp_registrable'
require 'wp_target/wp_api'
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_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
include WpTarget::WpReadme
include WpTarget::WpRegistrable
include WpTarget::WpAPI
include WpTarget::WpConfigBackup
include WpTarget::WpMustUsePlugins
include WpTarget::WpLoginProtection
include WpTarget::WpCustomDirectories
include WpTarget::WpFullPathDisclosure
include WpTarget::WpLoginProtection
include WpTarget::WpMustUsePlugins
include WpTarget::WpReadme
include WpTarget::WpRegistrable
include WpTarget::WpRSS
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 "ruby #{script_name} --url www.example.com --enumerate t"
puts
puts '-Enumerate users ...'
puts '-Enumerate users (from 1 - 10)...'
puts "ruby #{script_name} --url www.example.com --enumerate u"
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 "ruby #{script_name} --url www.example.com --enumerate tt"
puts
@@ -46,7 +49,7 @@ def usage
puts '-Use custom plugins directory ...'
puts "ruby #{script_name} -u www.example.com --wp-plugins-dir wp-content/custom-plugins"
puts
puts '-Update the DB ...'
puts '-Update the Database ...'
puts "ruby #{script_name} --update"
puts
puts '-Debug output ...'
@@ -110,7 +113,6 @@ def help
puts '--request-timeout <request-timeout> Request 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 '--throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.'
puts '--help | -h This help screen.'
puts '--verbose | -v Verbose output.'
@@ -118,6 +120,58 @@ def help
puts
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
# 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
@@ -136,3 +190,4 @@ Typhoeus.on_complete do |response|
sleep(Browser.instance.throttle)
end

View File

@@ -277,7 +277,6 @@ class WpscanOptions
['--cache-ttl', GetoptLong::REQUIRED_ARGUMENT],
['--request-timeout', GetoptLong::REQUIRED_ARGUMENT],
['--connect-timeout', GetoptLong::REQUIRED_ARGUMENT],
['--max-threads', GetoptLong::REQUIRED_ARGUMENT],
['--batch', GetoptLong::NO_ARGUMENT],
['--no-color', GetoptLong::NO_ARGUMENT],
['--cookie', GetoptLong::REQUIRED_ARGUMENT],

View File

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

View File

@@ -45,9 +45,18 @@ describe 'WpTheme::Findable' do
# FIXME: the style_url should be checked in WpTheme for absolute / relative
context 'when relative url is used' do
it 'returns the WpTheme' do
@file = 'relative_urls.html'
@expected = WpTheme.new(uri, name: 'theme_name')
context 'when leading slash' do
it 'returns the WpTheme' do
@file = 'relative_urls.html'
@expected = WpTheme.new(uri, name: 'theme_name')
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

View File

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

View File

@@ -207,18 +207,6 @@ describe 'WebSite' do
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
let(:log_url) { web_site.uri.merge('log.txt').to_s }
let(:pattern) { %r{PHP Fatal error} }

View File

@@ -1 +1 @@
<html><head><title>Test</title><link rel="stylesheet" type="text/css" href="/wp-content/themes/theme_name/style.css" /></head><body></body></html>
<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 @@
<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
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
url = uri.merge(readme).to_s
@expected = url
@@ -48,25 +48,42 @@ shared_examples 'WpItem::Infos' do
end
describe '#changelog_url' do
it 'returns the correct url' do
expect(subject.changelog_url).to eq changelog_url
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
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
describe '#has_changelog?' do
after :each do
stub_request(:get, subject.changelog_url).to_return(status: @status)
after do
allow(subject).to receive_messages(changelog_url: @stub)
expect(subject.has_changelog?).to eql @expected
end
it 'returns true on a 200' do
@status = 200
@expected = true
context 'when changelog_url is nil'
it 'returns false' do
@stub = nil
@expected = false
end
it 'returns false otherwise' do
@status = 404
@expected = false
context 'when changelog_url is not nil'
it 'returns true' do
@stub = uri.merge('changelog.txt').to_s
@expected = true
end
end

View File

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

250
wpscan.rb
View File

@@ -10,6 +10,7 @@ require File.join(__dir__, 'lib', 'wpscan', 'wpscan_helper')
def main
begin
wpscan_options = WpscanOptions.load_from_arguments
date = last_update
$log = wpscan_options.log
@@ -27,7 +28,7 @@ def main
# check if file exists and has a size greater zero
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.")
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
# unset logging so puts will try to log to the file
$log = nil
@@ -54,6 +55,8 @@ def main
unless wpscan_options.has_options?
# first parameter only url?
if ARGV.length == 1
puts
puts notice("Please use '-u #{ARGV[0]}' next time")
wpscan_options.url = ARGV[0]
else
usage()
@@ -72,8 +75,7 @@ def main
if wpscan_options.version
puts "Current version: #{WPSCAN_VERSION}"
date = last_update
puts "Last DB update: #{date.strftime('%Y-%m-%d')}" unless date.nil?
puts "Last database update: #{date.strftime('%Y-%m-%d')}" unless date.nil?
exit(0)
end
@@ -83,28 +85,44 @@ def main
wpscan_options.to_h.merge(max_threads: wpscan_options.threads)
)
# check if db file needs upgrade and we are not running in batch mode
# also no need to check if the user supplied the --update switch
if update_required? && !wpscan_options.batch && !wpscan_options.update
puts notice('It seems like you have not updated the database for some time.')
print '[?] Do you want to update now? [Y]es [N]o [A]bort, default: [N]'
if (input = Readline.readline) =~ /^y/i
# 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
if update_required? and not wpscan_options.batch and not wpscan_options.update
# Banner
puts
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
elsif input =~ /^a/i
puts 'Scan aborted'
exit(1)
else
if missing_db_file?
puts critical('You can not run a scan without any databases. Extract the data.zip file.')
end
# 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
puts critical('You can not run a scan without any databases')
exit(1)
end
end
end
# Should we update?
if wpscan_options.update
puts notice('Updating the Database ...')
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(0) unless wpscan_options.url
end
@@ -120,12 +138,18 @@ def main
end
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
# Remote website up?
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
if wpscan_options.proxy
@@ -145,7 +169,7 @@ def main
puts "Following redirection #{redirection}"
else
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
if wpscan_options.follow_redirection || !wpscan_options.batch
if wpscan_options.follow_redirection || (input = Readline.readline) =~ /^y/i
@@ -174,7 +198,7 @@ def main
# Remote website is wordpress?
unless wpscan_options.force
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
@@ -196,40 +220,9 @@ def main
start_memory = get_memory_usage unless windows?
puts info("URL: #{wp_target.url}")
puts info("Started: #{start_time.asctime}")
puts info("User-Agent: #{wpscan_options.user_agent}") if wpscan_options.verbose and wpscan_options.user_agent
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|
output = info('Interesting header: ')
@@ -242,6 +235,66 @@ def main
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?
puts info('This site seems to be a multisite (http://codex.wordpress.org/Glossary#Multisite)')
end
@@ -250,12 +303,35 @@ def main
puts info("This site has 'Must Use Plugins' (http://codex.wordpress.org/Must_Use_Plugins)")
end
if wp_target.registration_enabled?
puts warning("Registration is enabled: #{wp_target.registration_url}")
if wp_target.has_xml_rpc?
code = get_http_status(wp_target.xml_rpc_url)
puts info("XML-RPC Interface available under: #{wp_target.xml_rpc_url} [HTTP #{code}]")
end
if wp_target.has_xml_rpc?
puts info("XML-RPC Interface available under: #{wp_target.xml_rpc_url}")
# Test to see if MAIN API URL gives anything back
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
if wp_target.upload_directory_listing_enabled?
@@ -270,13 +346,20 @@ def main
show_progression: true,
exclude_content: wpscan_options.exclude_content_based
}
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
if wp_version = wp_target.version(WP_VERSIONS_FILE)
wp_version.output(wpscan_options.verbose)
else
puts
puts notice('WordPress version can not be detected')
end
if wp_theme = wp_target.theme
puts
@@ -295,7 +378,7 @@ def main
parent.output(wpscan_options.verbose)
wp_theme = parent
end
end
if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil
@@ -304,14 +387,11 @@ def main
wp_plugins = WpPlugins.passive_detection(wp_target)
if !wp_plugins.empty?
if wp_plugins.size == 1
puts " | #{wp_plugins.size} plugin found:"
else
puts " | #{wp_plugins.size} plugins found:"
end
grammar = grammar_s(wp_plugins.size)
puts " | #{wp_plugins.size} plugin#{grammar} found:"
wp_plugins.output(wpscan_options.verbose)
else
puts info('No plugins found')
puts info('No plugins found passively')
end
end
@@ -343,12 +423,14 @@ def main
puts
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)
else
puts info('No plugins found')
end
end
# Enumerate installed themes
@@ -378,12 +460,14 @@ def main
)
puts
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)
else
puts info('No themes found')
end
end
if wpscan_options.enumerate_timthumbs
@@ -393,18 +477,20 @@ def main
wp_timthumbs = WpTimthumbs.aggressive_detection(wp_target,
enum_options.merge(
file: DATA_DIR + '/timthumbs.txt',
file: TIMTHUMBS_FILE,
theme_name: wp_theme ? wp_theme.name : nil
)
)
puts
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)
else
puts info('No timthumb files found')
end
end
# If we haven't been supplied a username/usernames list, enumerate them...
@@ -432,7 +518,8 @@ def main
exit(1)
end
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)
if wp_users[0].login == "admin"
puts warning("Default first WordPress username 'admin' is still used")
@@ -442,10 +529,12 @@ def main
else
wp_users = WpUsers.new
# Username file?
if wpscan_options.usernames
File.open(wpscan_options.usernames).each do |username|
wp_users << WpUser.new(wp_target.uri, login: username.chomp)
end
# Single username?
else
wp_users << WpUser.new(wp_target.uri, login: wpscan_options.username)
end
@@ -455,7 +544,6 @@ def main
bruteforce = true
if wpscan_options.wordlist
if wp_target.has_login_protection?
protection_plugin = wp_target.login_protection_plugin()
puts
@@ -481,6 +569,7 @@ def main
else
puts critical('Brute forcing aborted')
end
end
stop_time = Time.now
@@ -489,9 +578,9 @@ def main
puts
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("Requests made: #{@total_requests_done}")
puts info("Memory used: #{used_memory.bytes_to_human}") unless windows?
# do nothing on interrupt
rescue Interrupt
@@ -501,14 +590,16 @@ def main
puts critical(e.message)
if e.file
puts critical("Current Version: #{WPSCAN_VERSION}")
puts critical('Downloaded File Content:')
puts e.file[0..500]
puts e.file[0..500] # print first 500 chars
puts '.........'
puts e.file[-500..-1] || e.file # print last 500 chars or the whole file if it's < 500
puts
end
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('-) Windows is still not supported')
exit(1)
@@ -522,10 +613,13 @@ def main
end
exit(1)
ensure
# Ensure a clean abort of Hydra
# See https://github.com/wpscanteam/wpscan/issues/461#issuecomment-42735615
Browser.instance.hydra.abort
Browser.instance.hydra.run
# Make sure there was an argument
if ARGV.length != 0
# Ensure a clean abort of Hydra
# See https://github.com/wpscanteam/wpscan/issues/461#issuecomment-42735615
Browser.instance.hydra.abort
Browser.instance.hydra.run
end
end
end