Compare commits

...

516 Commits
2.5 ... 2.9.4

Author SHA1 Message Date
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
Christian Mehlmauer
641108e7eb Stats 2017-07-19 15:24:32 +02:00
Christian Mehlmauer
0e87384b0a update data.zip 2017-07-19 15:05:41 +02:00
Christian Mehlmauer
5175170c4b prepare release 2017-07-19 14:59:33 +02:00
ethicalhack3r
79864cae7b Add emergency.php detection #1108 2017-07-17 20:56:38 +02:00
Christian Mehlmauer
ca5f92ca61 travis 2017-07-08 01:12:06 +02:00
Christian Mehlmauer
d29de83c41 prepare release, update gems 2017-07-08 01:10:00 +02:00
Christian Mehlmauer
1f42ce6e2f Merge pull request #1109 from zmwangx/readme-homebrew
Document Homebrew package in README
2017-07-07 09:39:30 +02:00
Zhiming Wang
0dc7128582 Document Homebrew package in README
Also, (Mac) OS X has been rebranded as macOS since June 2016, so rename that.
2017-07-06 20:13:57 -04:00
Christian Mehlmauer
21f4de2ec1 make logfile configurable 2017-05-31 23:16:07 +02:00
ethicalhack3r
d65567fc8f Remove previous version detection commit #1092 2017-05-02 16:13:54 +02:00
Christian Mehlmauer
20af778fa1 fix rspecs 2017-05-02 15:37:38 +02:00
ethicalhack3r
5f77832386 Improve version detection regex. Fix #1092 2017-05-02 12:30:16 +02:00
Christian Mehlmauer
6ccfe70775 install only supported gems 2017-04-21 20:07:02 +02:00
Christian Mehlmauer
6b0f687abb typo 2017-04-21 19:45:17 +02:00
Christian Mehlmauer
67ba526b5b use ruby alpine image from now on 2017-04-21 19:40:25 +02:00
ethicalhack3r
e186ec7534 Update install instruction for ruby 2.4.0 2017-04-20 16:35:11 +02:00
Christian Mehlmauer
23ef1e75b3 remove unneeded statement 2017-04-12 20:26:47 +02:00
Christian Mehlmauer
8170390f92 fix rspecs 2017-04-12 20:22:47 +02:00
Christian Mehlmauer
c148295f64 use Gemfile.lock from now on 2017-04-12 20:18:13 +02:00
Christian Mehlmauer
37b99f9baa Merge branch 'master' of github.com:wpscanteam/wpscan 2017-04-12 20:15:22 +02:00
Christian Mehlmauer
8e4643874d more docker work 2017-04-12 20:13:49 +02:00
Ryan Dewhurst
0522023fd4 Merge pull request #1081 from jamesalbert/master
--wordlist - reads stdin
2017-04-12 09:12:06 +02:00
jamesalbert
711ee730a0 updated readme 2017-04-11 09:58:43 -07:00
jamesalbert
f3bd995528 differentiate between stdin and file (estimating) 2017-04-11 03:20:11 -07:00
jamesalbert
beec0bd35a fixed progress_bar scope 2017-04-11 03:09:24 -07:00
jamesalbert
9d7f35f3b2 tightened up the threshold 2017-04-11 02:58:47 -07:00
jamesalbert
c7488e28f7 added estimation for stdin 2017-04-11 02:55:34 -07:00
jamesalbert
9150e0ca52 reads stdin line by line 2017-04-10 02:44:43 -07:00
jamesalbert
475288deeb --wordlist - reads stdin 2017-04-10 02:10:34 -07:00
erwanlr
82335d7399 Merge pull request #1075 from qutorial/master
Preciser reporting in bruteforcing password with bad response
2017-03-25 15:42:26 +00:00
Zaur
338eacd63b Preciser reporting in bruteforcing password with bad response
When bruteforcing for multiple logins and passwords the bad response code reported
might indicate a match! But the reporting for it is not clear enough.
For example "Unkown response for admin" might mean a user name admin and some password
or a password 'admin' for some user.

This commit makes in unambiguous reporting a bad response, and naming which login and
which password caused it.
2017-03-25 16:18:05 +01:00
Christian Mehlmauer
0b9b79f55f change tag 2017-03-24 18:55:49 +01:00
Christian Mehlmauer
5303b28957 add docker examples 2017-03-23 20:09:30 +01:00
Christian Mehlmauer
11c05a3590 some more help 2017-03-11 19:49:22 +01:00
Christian Mehlmauer
862c0a9014 binstub 2017-03-03 16:21:07 +01:00
Christian Mehlmauer
487a483aa6 gitignore 2017-01-31 22:03:43 +01:00
Christian Mehlmauer
030c20a11b travis 2017-01-31 22:02:53 +01:00
Christian Mehlmauer
ec831f7fed wtf? it was never required? 2017-01-31 22:02:20 +01:00
Christian Mehlmauer
50fa79b331 try to fix travis 2017-01-31 21:46:07 +01:00
Christian Mehlmauer
edab0e812a try to fix travis 2017-01-31 21:43:07 +01:00
Christian Mehlmauer
f0126ca860 try to fix travis 2017-01-31 21:36:00 +01:00
Christian Mehlmauer
01261d4d29 try to fix travis 2017-01-31 21:33:09 +01:00
Christian Mehlmauer
f97d3436a5 try to fix travis 2017-01-31 21:23:43 +01:00
Christian Mehlmauer
0bcb8b4b3b try to fix travis 2017-01-31 21:12:42 +01:00
Christian Mehlmauer
489545dd75 try to fix travis 2017-01-31 21:08:58 +01:00
Christian Mehlmauer
f6c152f58a update all gems to newest version 2017-01-31 20:36:32 +01:00
Christian Mehlmauer
16734418be Merge pull request #1053 from wpscanteam/revert-1052-master
Revert "Fix logic error in parsing command line args"
2017-01-29 23:16:42 +01:00
Christian Mehlmauer
b17ee20f58 Revert "Fix logic error in parsing command line args" 2017-01-29 23:16:01 +01:00
Ryan Dewhurst
aaee6f1e6d Merge pull request #1052 from petercunha/master
Fix logic error in parsing command line args
2017-01-29 21:26:58 +01:00
Peter Cunha
64d8240b8a Fix logic error in parsing command line args 2017-01-29 14:25:25 -05:00
Christian Mehlmauer
0a6d430c9f fix typo 2017-01-28 00:40:51 +01:00
Christian Mehlmauer
7bf0314561 try to fix travis 2017-01-17 20:47:01 +01:00
Christian Mehlmauer
409897fec4 fix travis and older ruby versions 2017-01-17 20:40:37 +01:00
Christian Mehlmauer
91b0d20665 forgot travis 2017-01-17 20:26:43 +01:00
Christian Mehlmauer
f6644eebf9 make wpscan ruby 2.4.0 compatible
fixes #1044
2017-01-17 20:24:32 +01:00
Ryan Dewhurst
88bddd4f87 Merge pull request #1046 from dctabuyz/fix__require_readline
'gem install readline' issues fix
2017-01-12 12:34:50 +01:00
dctabuyz
c61b023fb7 placing 'require readline' before require 'bundler/setup' fixes 'gem install readline' issues 2017-01-12 01:32:07 -05:00
Christian Mehlmauer
1b5df8751f Merge pull request #1045 from thijskh/patch-1
Add gcc to Debian prerequisites
2017-01-11 17:51:25 +01:00
Thijs Kinkhorst
314c98f101 Add gcc to Debian prerequisites
This is needed to install some gems and mirrors the fact that gcc is included in the command lines Fedora and Ubuntu (there contained in `build-essential`).
2017-01-11 17:19:27 +01:00
ethicalhack3r
8274e2efe9 Update to Ruby 2.3.3 2016-11-24 19:00:45 +01:00
ethicalhack3r
2bff063805 More changelog info 2016-11-15 20:51:38 +01:00
ethicalhack3r
53d9956829 Update data.zip 2016-11-15 20:37:54 +01:00
ethicalhack3r
6e98678c3c Bump wpscan version 2016-11-15 20:37:07 +01:00
ethicalhack3r
f0f21f5ac2 Add stats to changelog 2016-11-15 20:35:48 +01:00
ethicalhack3r
aa233b1c4d Add total vuln stats 2016-11-15 20:34:55 +01:00
ethicalhack3r
93f9123f45 Document missing options 2016-11-15 20:17:09 +01:00
ethicalhack3r
5c710d88e4 Update changelog 2016-11-15 20:00:54 +01:00
ethicalhack3r
ded70ff743 add R symbol 2016-11-08 14:03:33 +01:00
Christian Mehlmauer
9df7443aa4 color 2016-11-02 22:23:00 +01:00
Christian Mehlmauer
8362975691 apt tweak 2016-11-02 21:52:14 +01:00
Christian Mehlmauer
49771419ae Merge branch 'master' of github.com:wpscanteam/wpscan 2016-11-01 19:39:24 +01:00
Christian Mehlmauer
d344f84824 remove cloudflare error handling 2016-11-01 19:38:47 +01:00
Christian Mehlmauer
89c0b8d4d0 Merge pull request #1019 from wpscanteam/hash
remove scripts before calculating hashes
2016-10-26 11:48:13 +02:00
Christian Mehlmauer
3c74ee8d97 remove scripts before calculating hashes 2016-10-25 20:44:00 +02:00
ethicalhack3r
785c6efa5b Fix typo 2016-10-14 14:52:54 +02:00
ethicalhack3r
4e2bf5322e Markdown formating 2016-10-14 14:51:40 +02:00
ethicalhack3r
54ed148c87 Add passive detection of google-universal-analytics 2016-10-14 14:48:48 +02:00
Christian Mehlmauer
b08e298eba Merge branch 'master' of github.com:wpscanteam/wpscan 2016-10-06 20:35:44 +02:00
Christian Mehlmauer
89e2088357 fix #1008 2016-10-06 20:35:29 +02:00
ethicalhack3r
f3cc35bd74 trademark update 2016-09-08 09:39:52 +02:00
Christian Mehlmauer
a007d283e5 rspecs 2016-09-05 23:25:33 +02:00
Christian Mehlmauer
70902aa013 Merge branch 'master' of github.com:wpscanteam/wpscan 2016-09-05 22:59:14 +02:00
Christian Mehlmauer
91151fc53b check for ssl related errors. Fix #993 2016-09-05 22:58:56 +02:00
Christian Mehlmauer
d4ee82dac5 Update README.md 2016-08-17 18:31:35 +02:00
Christian Mehlmauer
88d3c26113 moar rspecs 2016-08-16 21:40:19 +02:00
Christian Mehlmauer
054a4ee6aa fix #984 2016-08-16 21:20:29 +02:00
ethicalhack3r
c291022753 Improve yoast seo pasive detection regex #984 2016-08-16 17:20:52 +02:00
Christian Mehlmauer
2fc488b602 rework readme 2016-08-15 00:25:46 +02:00
Christian Mehlmauer
009ddd690e verbose update 2016-08-13 12:52:33 +02:00
Christian Mehlmauer
88b5cd8751 readme 2016-08-13 10:30:06 +02:00
Christian Mehlmauer
cfd19d02b1 readme 2016-08-13 10:29:28 +02:00
Christian Mehlmauer
19ce30d862 trigger docker build 2016-08-13 10:27:52 +02:00
Christian Mehlmauer
c6df6e0e89 move docker stuff 2016-08-13 10:24:02 +02:00
Christian Mehlmauer
e942a5bcf6 Exit on exceptions 2016-08-12 23:56:36 +02:00
Christian Mehlmauer
c0f5163d07 handle null 2016-08-12 21:50:59 +02:00
Christian Mehlmauer
f5aa9f117f fix #968 2016-08-12 21:29:05 +02:00
Christian Mehlmauer
498d93377d rvm install instructions 2016-08-12 21:25:45 +02:00
Christian Mehlmauer
52242e706b Merge branch 'master' of github.com:wpscanteam/wpscan 2016-08-12 20:55:20 +02:00
Christian Mehlmauer
22d69a1bf9 more detailed update exception 2016-08-12 20:54:24 +02:00
Ryan Dewhurst
0b1fa13696 Merge pull request #973 from pierre-dargham/feature_option_cache
Enable --cache-dir option in command line parameters, which solves write permission issues when wpscan is installed in system or root-owned directories
2016-08-12 12:16:11 +02:00
Christian Mehlmauer
19b15b5327 travis 2016-08-08 22:35:20 +02:00
Christian Mehlmauer
e63e96f5ed travis 2016-08-08 22:04:42 +02:00
Christian Mehlmauer
e8ac8f26a7 travis 2016-08-08 22:00:52 +02:00
Christian Mehlmauer
13e4327de4 travis 2016-08-08 21:57:38 +02:00
Christian Mehlmauer
c22a1ed12a travis 2016-08-08 21:55:40 +02:00
Christian Mehlmauer
be5662b5f1 travis 2016-08-08 21:52:30 +02:00
Christian Mehlmauer
6e840ca920 fix #974 2016-08-08 21:40:36 +02:00
Pierre Dargham
8492190f4c Allow --cache-dir option in command line parameters 2016-08-05 10:56:40 +02:00
Christian Mehlmauer
93ab6ee2a0 fucking specs 2016-08-01 22:13:38 +02:00
Christian Mehlmauer
7075e01886 Merge branch 'master' of github.com:wpscanteam/wpscan 2016-08-01 22:07:47 +02:00
Christian Mehlmauer
436a83434c fix #972 2016-08-01 22:04:13 +02:00
pvdl
d270391b56 Fix for missing 'zlib.h' in Nokogiri 2016-07-26 19:43:45 +02:00
Christian Mehlmauer
7f2762eb6f new options 2016-07-21 21:27:21 +02:00
Christian Mehlmauer
2cc5bb0311 fix rspecs 2016-07-21 13:57:18 +02:00
Christian Mehlmauer
d697127261 set user agent globally 2016-07-21 13:21:07 +02:00
Christian Mehlmauer
825523a851 changelog 2016-06-27 16:07:40 +02:00
Christian Mehlmauer
0f3f9cac33 more info 2016-06-24 21:17:43 +02:00
ethicalhack3r
f9b545b100 Clearer instructions 2016-06-23 13:40:15 +02:00
Christian Mehlmauer
943bfc39b3 fix for #957 2016-06-14 03:30:17 +02:00
Ryan Dewhurst
b1a8f445c6 Merge pull request #950 from anthraxx/master
bump terminal-table to 1.6.0 and drop workaround
2016-06-07 09:54:42 +02:00
anthraxx
5435df4345 bump terminal-table to 1.6.0 and drop workaround 2016-06-06 19:28:40 +02:00
ethicalhack3r
8e9d29e94f Update dependencies #939 2016-06-02 11:21:07 +02:00
ethicalhack3r
1afa761f09 RandomStorm is no more 2016-06-02 11:09:10 +02:00
Ryan Dewhurst
d626913ce9 Merge pull request #949 from wpscanteam/finders
more advanced version detection
2016-06-02 11:04:38 +02:00
ethicalhack3r
9c52e4a5ee Update dependencies #939 2016-06-02 11:03:07 +02:00
Christian Mehlmauer
72c2c1992b rspec fixed 2016-05-31 15:23:34 +02:00
Christian Mehlmauer
e1b4b5e8e5 typo 2016-05-31 14:53:50 +02:00
Christian Mehlmauer
0243522854 more advanced version detection 2016-05-31 14:51:09 +02:00
Christian Mehlmauer
5118c68f45 fix #943 2016-05-13 21:23:22 +02:00
Christian Mehlmauer
442884b5c5 remove executable flags 2016-05-09 16:19:11 +02:00
Christian Mehlmauer
f832e27b49 correct stats an correct data files 2016-05-06 11:52:05 +02:00
ethicalhack3r
6ce29f73c5 Update with correct stat #935 2016-05-06 11:35:57 +02:00
ethicalhack3r
920338fb62 Prepare 2.9.1 release #935 2016-05-06 00:15:53 +02:00
Christian Mehlmauer
49d0a9e6d9 check directory listing in wp-includes 2016-05-05 00:01:52 +02:00
Christian Mehlmauer
fe401e622b add stats 2016-05-04 23:09:00 +02:00
Christian Mehlmauer
6e32cb0db2 changelog 2016-05-04 22:46:02 +02:00
Ryan Dewhurst
73171eb39d Merge pull request #929 from wpscanteam/wp_metadata
WP Metadata Integration
2016-04-28 14:35:43 +02:00
ethicalhack3r
2e05f4171e Update to Ruby 2.3.1 2016-04-28 14:04:54 +02:00
Christian Mehlmauer
75b8c303e2 more verbose error 2016-04-27 15:19:07 +02:00
Christian Mehlmauer
bd7a493f1c travis errors 2016-04-20 20:49:17 +02:00
Christian Mehlmauer
9dada7c8f4 travis errors 2016-04-20 20:41:46 +02:00
ethicalhack3r
fe7aede458 Better output 2016-04-20 13:39:05 +02:00
ethicalhack3r
cdf2b38780 Only show changelog if verbose 2016-04-20 13:09:02 +02:00
ethicalhack3r
a09dbab6a8 Use db_file 2016-04-20 12:43:56 +02:00
ethicalhack3r
49a6d275d2 Update comment 2016-04-20 12:37:46 +02:00
ethicalhack3r
8192a4a215 Fix typo 2016-04-20 12:27:09 +02:00
ethicalhack3r
1d6593fd4d Add WP metadata #704 2016-04-20 12:02:15 +02:00
Christian Mehlmauer
bf99e31e70 higher update timeout 2016-04-20 09:33:56 +02:00
Christian Mehlmauer
5386496bdc move wordpress check to the top 2016-04-06 14:13:56 +02:00
Christian Mehlmauer
6451510449 new ruby version with security bugfixes released 2016-04-03 00:34:52 +02:00
Christian Mehlmauer
cd68aa719c possible fix for timeouts 2016-04-01 11:52:13 +02:00
Christian Mehlmauer
b328dc4ff9 possible fix for #912 2016-03-11 09:28:42 +01:00
Christian Mehlmauer
1e1c79aa56 Merge pull request #909 from wpscanteam/ruby_version
drop ruby 1.9 and 2.0 support, whitespaces
2016-02-26 14:08:38 +01:00
Christian Mehlmauer
08650ce156 fix travis 2016-02-25 06:39:47 +01:00
Christian Mehlmauer
a1929719f3 version 2.1.8 minimum requirement 2016-02-24 23:48:50 +01:00
Christian Mehlmauer
d34da72cd3 ruby 2.0.0 is EOL 2016-02-24 23:41:32 +01:00
Christian Mehlmauer
816b18b604 drop ruby 1.9 support, whitespaces 2016-02-23 18:07:20 +01:00
Christian Mehlmauer
a78a13bf3f revert change 2016-02-18 00:02:55 +01:00
Christian Mehlmauer
33f8aaf1dc Merge branch 'master' of github.com:wpscanteam/wpscan 2016-02-17 23:30:45 +01:00
Christian Mehlmauer
26ab95d822 more actual gems 2016-02-17 23:30:28 +01:00
erwanlr
cea01d8aa0 Improves brute forcer output to avoid confustions 2016-02-13 16:44:29 +00:00
Ryan Dewhurst
0e61f1e284 Merge pull request #901 from wpscanteam/new_urls
add new urls
2016-02-06 22:26:25 +01:00
Christian Mehlmauer
ddef061b90 add new urls 2016-02-05 22:25:18 +01:00
erwanlr
addeab8947 Fixes #900 2016-02-04 20:37:13 +01:00
erwanlr
55dc665404 Better specs 2016-01-11 16:33:29 +00:00
erwanlr
8f8538e9e9 Changes the order of the WP version from stylesheets check - Fixes #865 2016-01-11 16:27:22 +00:00
Christian Mehlmauer
348ca55bee copyright 2016-01-08 23:54:04 +01:00
Christian Mehlmauer
1bb5bc7f33 fix rspec 2016-01-03 21:28:02 +01:00
ethicalhack3r
3be5e1fcf5 Add Windows OS detection 2016-01-03 20:15:11 +01:00
Christian Mehlmauer
9df8cc9243 Update README.md 2016-01-02 10:57:55 +01:00
Christian Mehlmauer
e28c84aa34 Update fedore install instructions
See #886
2016-01-02 10:52:23 +01:00
Christian Mehlmauer
7db6b54761 Merge pull request #894 from nonmadden/update-ruby
Update to Ruby 2.3.0
2015-12-31 10:22:47 +01:00
nonmadden
e3a06f5694 Update to Ruby 2.3.0 2015-12-31 10:41:04 +07:00
erwanlr
7c5d15e098 Updates Nokogiri dep 2015-12-18 18:59:32 +01:00
ethicalhack3r
d683c0f151 Update to Ruby 2.2.4 2015-12-18 11:13:41 +01:00
erwanlr
1e67fa26ff Fixes #890 2015-11-26 14:12:04 +00:00
erwanlr
0ae6ef59ec Fixes an issue with --cache-ttl being a Strig instead of an integer 2015-11-26 13:52:12 +00:00
erwanlr
e27ef40e0f Updates Nokogiri dep version 2015-11-26 11:53:13 +00:00
ethicalhack3r
380760d028 Onlt shoe theme description when there is one 2015-10-26 16:06:13 +01:00
ethicalhack3r
18cfdafc19 Fix typo in options 2015-10-15 16:28:42 +02:00
ethicalhack3r
0934a2e329 Recommend RVM in readme 2015-10-15 15:51:38 +02:00
ethicalhack3r
d1a320324e Update reame CLI options 2015-10-15 15:49:18 +02:00
ethicalhack3r
361c96d746 Version 2.9 release 2015-10-15 13:01:53 +02:00
erwanlr
e7dbf9278d Fixes #873 - mu-plugins detection 2015-10-13 13:17:22 +01:00
erwanlr
6564fddb27 Adds a reminder about updating the terminal-table version 2015-10-13 13:12:12 +01:00
erwanlr
d382874e86 Fixes incorrect detection of the FDP data 2015-10-12 12:57:20 +01:00
erwanlr
91b30bee9f Updates Typhoeus dependency 2015-10-09 19:03:37 +02:00
erwanlr
7804aad776 Removes useless stuff & update the --throttle options text 2015-10-07 22:09:23 +01:00
erwanlr
b7552ac8aa Tried to throttle things 2015-10-07 19:03:52 +01:00
erwanlr
a76c94cccf Let's try Travis container-based infra & caching 2015-09-18 16:13:37 +02:00
Christian Mehlmauer
c0ae5c7cad Merge pull request #864 from wpscanteam/apiv2
new dependency
2015-09-11 21:09:51 +02:00
Christian Mehlmauer
cc55b39b83 new dependency 2015-09-11 15:31:29 +02:00
ethicalhack3r
d8a6884ab6 Only show 'up to date' string when version found 2015-09-09 15:46:44 +02:00
Ryan Dewhurst
5ce3581386 Merge pull request #862 from wpscanteam/apiv2
Apiv2
2015-09-08 21:00:03 +02:00
ethicalhack3r
2208f2a8c0 Implement lesser? method #862 2015-09-08 17:54:32 +02:00
ethicalhack3r
a4a14c7e63 Better version output #862 2015-09-08 17:24:10 +02:00
erwanlr
aa464b476c Fixes a bug where -e vp was displaying non vulnerable plugins - Ref #853 2015-09-06 15:25:29 +01:00
erwanlr
3c92712a6e Uses yajl as JSON parser to reduce memory used 2015-09-06 14:29:41 +01:00
erwanlr
fd0c47f5d7 Adds the latest_version, last_updated and popular? attributes - Ref #853 2015-09-06 14:26:36 +01:00
erwanlr
c03a44d225 Removes useless code 2015-09-06 13:32:13 +01:00
ethicalhack3r
d31d45ba71 Remove unneede newline 2015-09-05 14:10:08 +02:00
ethicalhack3r
db528b27f4 Implement Erwan's feedback #853 2015-09-05 13:49:03 +02:00
ethicalhack3r
e6d29f6f18 New json structure implemented #853 2015-09-03 22:04:44 +02:00
Christian Mehlmauer
e4d6b988ef forgot spec file, #858
Signed-off-by: Christian Mehlmauer <firefart@gmail.com>
2015-08-22 21:52:55 +02:00
Christian Mehlmauer
ec68291bf0 fix #858 2015-08-22 21:50:31 +02:00
ethicalhack3r
3a6a451db1 Update to Ruby 2.2.3 2015-08-21 09:41:06 +02:00
Christian Mehlmauer
7ec095d708 fix duplicate robots.txt entries 2015-08-18 15:55:10 +02:00
ethicalhack3r
57f6206aee Implement Erwan's feedbaxk #853 2015-08-14 21:51:55 +02:00
ethicalhack3r
390f10e83f Remove ArchAssault, 'had to close its doors' 2015-08-14 19:26:52 +02:00
ethicalhack3r
8727935cb2 Fix specs #853 2015-08-14 16:33:57 +02:00
ethicalhack3r
d0e868f556 Enable rspec fail-fast #853 2015-08-14 16:04:26 +02:00
ethicalhack3r
01c357e146 Fix specs #853 2015-08-14 16:03:21 +02:00
ethicalhack3r
a0fed4a9d0 Clean up last commit #853 2015-08-14 00:22:48 +02:00
ethicalhack3r
c4aed0ec89 Initial attempt at implementing apiv2 #853 2015-08-14 00:19:22 +02:00
erwanlr
cc737090a2 Fixes incorrect detection of the username 2015-08-13 10:27:33 +01:00
erwanlr
1652c09e95 Merge pull request #850 from mikicaivosevic/master
Re-factorises a statement
2015-08-12 14:53:43 +01:00
erwanlr
2538b88579 Adds the Accept-Encoding header when updating the DBs - Fixes #852 2015-08-12 14:50:14 +01:00
Mikica Ivosevic
8c2eb63840 update wp_target.rb
Refactor if else statement - wp_content_dir (credits: ethicalhack3r)
2015-07-28 12:41:09 +02:00
erwanlr
36df5ee6e4 Comments debug statement 2015-07-23 14:15:46 +01:00
erwanlr
9720b4edf1 Escapes brackets etc potentially present in Dir.pwd When using Dir.glob - Fixes #840 2015-07-23 14:15:04 +01:00
Christian Mehlmauer
13d35b7607 update email 2015-07-08 14:29:18 +02:00
Christian Mehlmauer
13c2c51cfd update email adress 2015-07-08 13:45:47 +02:00
ethicalhack3r
f43175b0c3 Use older terminal-table gem #841 2015-07-02 10:48:34 +02:00
erwanlr
1508aba8b2 Uses terminal-table 1.5.1 - Fixes #839 2015-06-28 13:54:25 +01:00
erwanlr
5414ab05e5 Restraints terminal-table version - Ref #839 2015-06-27 09:23:26 +01:00
erwanlr
bd5d2db634 Fixes #836 2015-06-26 09:24:17 +01:00
erwanlr
3259dd29d8 Merge pull request #833 from stefancastille/master
Adds a --vhost option (Virtualhost support)
2015-06-26 09:14:39 +01:00
stefancastille
6e56013a95 Update browser.rb 2015-06-25 16:18:04 +02:00
stefancastille
252f762209 Update wp_target.rb 2015-06-25 16:17:03 +02:00
stefancastille
15c0448cf1 Update wpscan_options.rb 2015-06-25 16:13:04 +02:00
erwanlr
4c800bacaa Fixes #835 2015-06-24 11:46:06 +01:00
ethicalhack3r
5902a483b4 Ready for release version 2.8 #834 2015-06-22 18:56:37 +02:00
Christian Mehlmauer
ca73e4b93e fix some code styling issues 2015-06-21 11:05:25 +02:00
Christian Mehlmauer
ace64d88ce Merge branch 'master' of github.com:wpscanteam/wpscan 2015-06-21 11:03:55 +02:00
Christian Mehlmauer
4cc9f7c8b5 merge 2015-06-21 11:03:51 +02:00
Christian Mehlmauer
f4f1390b67 fix some code styling issues 2015-06-21 10:59:57 +02:00
erwanlr
14115761f9 Uses the URI.join to determine the redirection URL - Fix #829 2015-06-18 20:48:43 +01:00
Peter
ac3409e376 Update CHANGELOG 2015-06-18 21:07:12 +02:00
stefancastille
86a73229c0 Update wp_target.rb 2015-06-17 08:46:14 +02:00
stefancastille
cc41b96e88 Update wpscan_options.rb 2015-06-17 08:44:50 +02:00
stefancastille
e16c5584d1 Update wpscan_options.rb 2015-06-17 08:44:04 +02:00
stefancastille
94bab3f550 Update wpscan_options.rb
Add support for virtual hosts
2015-06-17 08:42:59 +02:00
stefancastille
9d04b23fb2 Update browser.rb
add support for virtual hosts
2015-06-16 17:23:25 +02:00
Ryan Dewhurst
2657e5050f Merge pull request #830 from mrnfrancesco/fix-issue-815
Fix issue 815
2015-06-04 09:46:26 +02:00
ethicalhack3r
3d6e5b2b9e Continue if user chooses not to update + db exists 2015-06-03 16:42:23 +02:00
ethicalhack3r
bdd6b9727d Dont update if user chooses default + no DBs exist 2015-06-03 16:40:04 +02:00
Francesco Marano
6c8172c7cf Removed Time.parse('2000-01-01') expedient 2015-06-03 16:03:01 +02:00
Francesco Marano
ae5bae9899 Capitalised 'Last db update' in 'Last DB update' 2015-06-03 15:52:33 +02:00
Francesco Marano
b6bf306042 Removed unnecessary 'return' and '()' 2015-06-03 15:43:58 +02:00
Francesco Marano
9c5196dfec Added last db update to --version option (see #815) 2015-06-03 15:33:14 +02:00
Francesco Marano
3d7b8592ea Defined function to get last db update and removed redundant code 2015-06-03 15:32:34 +02:00
Christian Mehlmauer
e03f7691f2 switch to mitre 2015-05-24 09:02:26 +02:00
Christian Mehlmauer
7a54ac62d6 output path 2015-05-21 23:16:33 +02:00
Christian Mehlmauer
8db06d37d2 check if method exist 2015-05-16 08:21:32 +02:00
Christian Mehlmauer
5ee5e76544 new link types 2015-05-15 22:34:24 +02:00
Christian Mehlmauer
090cd999cb fix rspec 2015-05-12 22:36:07 +02:00
Christian Mehlmauer
50b75354e0 #796, do not swallow exit code 2015-05-12 21:51:15 +02:00
Christian Mehlmauer
c7b6b25851 removed debug output 2015-05-12 21:29:21 +02:00
Christian Mehlmauer
b931df654d fix #796 2015-05-12 21:28:12 +02:00
erwanlr
b5d5c4177d Removes potential spaces in robots.txt entries - Ref #819 2015-05-08 09:50:51 +01:00
Christian Mehlmauer
b22550ea55 fix #814 2015-05-01 22:15:58 +02:00
Christian Mehlmauer
04d50ebea5 more logic 2015-05-01 13:14:23 +02:00
Christian Mehlmauer
202180909c warn the user to update his DB files 2015-05-01 11:29:03 +02:00
erwanlr
0d806e6d74 Ignores potential non version chars in theme version detection - Fixes #816 2015-05-01 09:56:18 +01:00
erwanlr
54f31ebe7f Merge branch 'master' of github.com:wpscanteam/wpscan 2015-05-01 09:50:45 +01:00
erwanlr
227a39d2fa Updates the theme detection pattern - Ref #816 2015-05-01 09:50:20 +01:00
Christian Mehlmauer
99d8faa38b switch from gnutls to openssl 2015-04-30 23:45:10 +02:00
Christian Mehlmauer
9a7afe1549 option to hide banner 2015-04-30 21:39:03 +02:00
erwanlr
e6751e0d89 Remove potential new line at the end of .sha512 files during the update 2015-04-25 15:27:13 +01:00
ethicalhack3r
371f1df830 Remove www subdomain from wpvulndb.com link 2015-04-24 10:12:15 +02:00
Peter
8e1ba352ee Singular and plural sentences 2015-04-21 20:33:32 +02:00
ethicalhack3r
7ebfe42eb2 Install bundler gem README 2015-04-17 16:25:17 +02:00
ethicalhack3r
df514d3b9f Update to Ruby 2.2.2 2015-04-16 18:52:25 +02:00
erwanlr
acae16e7ee Adds the missing spec file - Ref #804 2015-04-15 18:38:57 +01:00
erwanlr
deb8508ea5 Updates the Theme detection pattern - Fixes #804 2015-04-15 18:37:23 +01:00
erwanlr
a4bbf41086 Forces UTF-8 encoding when enumerating usernames - Fixes #801 2015-04-11 12:26:15 +01:00
erwanlr
4fbc535b0c Increases default connect-timeout to 10s - Fixes #803 2015-04-10 16:58:21 +01:00
Ryan Dewhurst
36f6f98ce7 Merge pull request #802 from wpscanteam/remove_wpstoools
Remove wpstools #793
2015-04-10 14:29:57 +02:00
ethicalhack3r
21cc7d604c Remove wpstools #793 2015-04-10 13:43:11 +02:00
erwanlr
44207161e6 Also check for potential timed out requests when updating - Ref #797 2015-04-03 17:48:59 +01:00
erwanlr
dc20ef0754 Increases the timeout values - Ref #797 2015-04-03 17:10:07 +01:00
erwanlr
413ee7a6d3 Adds the HttpError exception - Fixes #792 2015-04-03 16:22:28 +01:00
Christian Mehlmauer
5b94714ca7 remove GHOST warning, fixes #795 2015-04-03 17:00:17 +02:00
Christian Mehlmauer
3675fe1ed7 whitespace 2015-04-03 16:45:41 +02:00
erwanlr
e074a03c40 Fixes Indentation 2015-04-03 12:29:27 +01:00
erwanlr
a7860f72a2 Merge pull request #798 from surfer190/master
Add db checksum to verbose logging during update
2015-04-03 12:25:16 +01:00
surfer190
4b587593ee Add db checksum to verbose logging during update 2015-04-03 10:27:26 +02:00
Christian Mehlmauer
0aa8a97070 additional output 2015-04-02 07:17:58 +02:00
Christian Mehlmauer
3c16f84853 even more output 2015-04-02 00:34:44 +02:00
Christian Mehlmauer
346898e549 more output 2015-04-02 00:21:53 +02:00
erwanlr
bcef4b2de7 Fixes #791 - Rogue character causing the scan of non-wordpress site to crash 2015-04-01 13:09:10 +01:00
erwanlr
e42bf7fd7c Consider the target down after 30 requests timed out requests instead of 10 - Fixes 790 2015-04-01 09:25:17 +01:00
Christian Mehlmauer
48cd0602d8 do not build gh-pages branch 2015-03-30 22:00:39 +02:00
Christian Mehlmauer
814e837ae5 No rdoc and no ri for gems 2015-03-30 21:58:28 +02:00
erwanlr
a58b34eba8 Updates request timeout values to realistic ones (and in seconds) 2015-03-30 16:08:49 +01:00
ethicalhack3r
7d790f8f79 Add blackarch to readme. Fix #789 2015-03-30 16:44:27 +02:00
ethicalhack3r
7cf06f4989 Updated data file #784 2015-03-16 18:35:57 +01:00
ethicalhack3r
61381b7168 Update changelog, change version number #784 2015-03-16 10:49:54 +01:00
Christian Mehlmauer
df598c5900 fix for custom content dir 2015-03-14 16:03:48 +01:00
ethicalhack3r
aed74e029a Update Ruby to 2.2.1 2015-03-03 15:09:32 +01:00
erwanlr
6e01e1b9da Merge pull request #774 from berotti3/berotti3-wpscan
Updates the Username detection pattern
2015-02-21 12:56:06 +00:00
berotti3
42f278aafe Available take username for wordpress 3.0 or lower. 2015-02-21 15:48:28 +09:00
Christian Mehlmauer
884f64addb move version detection to seperate function and change line endings 2015-02-18 18:37:47 +01:00
erwanlr
0c9cf4ddd5 Changes the GHOST warning message to a notice one - Fixes #771 2015-02-13 16:54:02 +01:00
erwanlr
f6dfe0e8dd Avoid iterating over all the vuln items once the right one has been found 2015-02-12 18:57:16 +01:00
ethicalhack3r
9f4ca1add7 Update databases from wpvulnsb.com 2015-02-06 00:21:13 +01:00
ethicalhack3r
1f6edc5852 Add link to ghost msf module #763 2015-02-02 15:36:27 +01:00
ethicalhack3r
a74017f595 Fix #764 2015-01-30 16:18:28 +01:00
Ryan Dewhurst
89bc7609ea Merge pull request #762 from Pablohn26/patch-1
Add patch dependency to fedora installation
2015-01-30 15:45:20 +01:00
ethicalhack3r
2c93c8ef6d Update Ruby version in RVM docs 2015-01-30 15:32:03 +01:00
erwanlr
bfe370fa50 Adds a line about GHOST when XMLRPC is enabled, Fixes #763 2015-01-30 12:02:59 +01:00
Pablo Hinojosa
3b4850e1ba Add patch dependency to fedora installation 2015-01-28 18:52:02 +01:00
erwanlr
b2d1c25b8e Uses inline if 2015-01-26 18:19:49 +01:00
erwanlr
093598ac99 Fixes #760 2015-01-26 18:16:50 +01:00
erwanlr
585d22be46 Adds security-protection plugin detection - Fixes #747 2015-01-25 15:16:11 +01:00
erwanlr
9361cf4b00 Adds a global requests counter - Fixes #746 2015-01-22 21:08:09 +01:00
erwanlr
298e9130dd Fixes #754 2015-01-22 19:48:01 +01:00
Christian Mehlmauer
41ae47f065 sync license 2015-01-21 21:53:54 +01:00
ethicalhack3r
41f7fe1554 Markdown formatting 2015-01-21 17:57:06 +01:00
ethicalhack3r
965be1c0f3 New license 2015-01-21 17:52:34 +01:00
Ryan Dewhurst
fa8ac37e8b New LICENSE 2015-01-21 17:50:02 +01:00
Christian Mehlmauer
d7975b6192 version detection 2015-01-20 15:14:32 +01:00
Christian Mehlmauer
0a0fe55427 improve regex and more samples 2015-01-20 00:35:46 +01:00
Christian Mehlmauer
8e08a20178 missing ? 2015-01-20 00:06:34 +01:00
Christian Mehlmauer
9dd44808ec detect even more 2015-01-20 00:04:49 +01:00
Christian Mehlmauer
507cf1d511 fix regex 2015-01-19 23:41:51 +01:00
Christian Mehlmauer
53f3ce8b1f advanced version detection 2015-01-19 23:38:26 +01:00
erwanlr
2d39e5b1fa Ensures timeouts given to Typhoeus are Integers - Fixes #753 2015-01-18 20:14:41 +01:00
ethicalhack3r
60716dcf81 Update CREDITS 2015-01-11 12:06:51 +01:00
Christian Mehlmauer
82141c2535 refine version detection regex 2015-01-08 23:42:15 +01:00
Christian Mehlmauer
3d6de3fe75 refine version detection regex 2015-01-08 23:34:19 +01:00
erwanlr
03ab396353 Ensures that the version detected by stylesheets is present more than once. Ref #478 2015-01-08 23:26:36 +01:00
erwanlr
6221601376 Fixes a typo 2015-01-08 22:53:14 +01:00
erwanlr
71fdef45c9 Adds passive WP version detection from stylesheets. Fix #478 - Ref #750 2015-01-08 20:45:15 +01:00
ethicalhack3r
147a9e4968 Recommend random-agent on 403 2015-01-08 15:07:02 +01:00
ethicalhack3r
8f7b56da32 Fix typo 2015-01-08 14:15:34 +01:00
ethicalhack3r
4ef2452083 Update Typhoeus to 0.7.0 2015-01-08 14:14:07 +01:00
erwanlr
70cfa03ee8 Adds Addressable to the Gemfile 2015-01-07 10:35:26 +01:00
erwanlr
5bd3d4fd96 Merge pull request #749 from dctabuyz/master
wp_must_use_plugins.rb fix page hash calculation & encode IDN
2015-01-07 10:33:36 +01:00
dctabuyz
c0fe02efb9 Merge pull request #2 from dctabuyz/useActualRubyVersion
use actual ruby interpreter
2015-01-07 14:03:54 +05:00
dctabuyz
b0f4843526 Merge pull request #1 from dctabuyz/encodeIDN
IDN support: encode non-ascii domain names
2015-01-07 14:03:50 +05:00
dctabuyz
a9e161268c IDN support: encode non-ascii domain names 2015-01-07 12:55:26 +05:00
dctabuyz
cbad8857bd use actual ruby interpreter 2015-01-07 12:34:27 +05:00
dctabuyz
5adefda286 Digest::MD5.hexdigest replaced by WebSite.page_hash 2015-01-06 23:05:57 +03:00
dctabuyz
265bfcd7c8 calculate page hash only if response code is valid 2015-01-06 19:11:57 +03:00
dctabuyz
b81a4987d9 fix page hash calculation 2015-01-06 19:10:22 +03:00
ethicalhack3r
6b9c9eb0ed Build icon pointing to CMSScanner 2015-01-02 18:57:14 +01:00
ethicalhack3r
4f82d618dc Ruby 2.2.0 changes #748 2015-01-02 18:19:07 +01:00
ethicalhack3r
b7f7bdb9ac Fix specs #748 2015-01-02 18:17:45 +01:00
ethicalhack3r
c5136fd330 Update copyright date range 2015-01-02 17:00:13 +01:00
Peter
e7e0e886fc Better readable sentence 2015-01-02 13:09:11 +01:00
erwanlr
42e8ab1680 Updates the version pattern to allow letters in the format - Ref #745 2015-01-01 20:13:33 +01:00
erwanlr
ab7b7de60a Detects version in a release date format - Fixes #745 2015-01-01 19:45:10 +01:00
erwanlr
21221d48d0 Bumps the version 2014-12-19 17:26:11 +01:00
Peter
1f1a190c84 Update changelog 2014-12-19 13:25:59 +01:00
ethicalhack3r
82d79c4662 Add statistics to changelog #740 2014-12-19 12:57:55 +01:00
ethicalhack3r
08771a6d5d Update data file #740 2014-12-19 12:53:42 +01:00
ethicalhack3r
e01d18f224 Add RS as special thanks #736 2014-12-19 12:20:47 +01:00
ethicalhack3r
8496650542 Remove references to pyfiscan 2014-12-19 12:19:20 +01:00
Peter
399245cd0f Prepare changelog for next release 2014-12-19 12:04:40 +01:00
ethicalhack3r
adfa5dddcf Remove unneeded code 2014-12-15 16:01:24 +01:00
ethicalhack3r
85971e0e91 Refactor #703 #336 2014-12-15 16:00:30 +01:00
ethicalhack3r
3a3376ec41 Disable logging by default. Implement log option. #703 #336 2014-12-15 15:39:14 +01:00
Christian Mehlmauer
d988b6ccbf fix all the rspecs 2014-12-15 13:12:19 +01:00
Christian Mehlmauer
6654f446a4 try to fix #741 2014-12-15 12:37:46 +01:00
erwanlr
88808db9a5 Fixes the progressbar being overriden by next brute forcing attempts 2014-12-10 22:05:35 +01:00
erwanlr
dfad0fd6bd Implements Firefart solution to avoid mandatory blank newline at the end of the wordlist 2014-12-10 21:56:45 +01:00
erwanlr
3fe49a24c7 Updates the readmes to reflect the new --usernames option 2014-12-10 18:17:04 +01:00
erwanlr
ac609445fb Adds a statement about mendatory newlines at the end of list - Ref #739 2014-12-10 18:11:12 +01:00
erwanlr
0223f74a53 Adds the --usernames option - Fixes #739 2014-12-10 17:59:07 +01:00
erwanlr
607a5b3fda Adds the --usernames option - Fixes #739 2014-12-10 17:58:51 +01:00
erwanlr
e3ac331a71 Removes the theme version check from the readme, unrealistic scenario - Ref #737 2014-12-10 17:01:14 +01:00
erwanlr
e09b4cc76d Adds some readme files to check for plugin versions - Fixes #737 2014-12-10 16:55:00 +01:00
erwanlr
c24ed707ef Improves plugin/theme version detection by looking at the "Version: "- Fixes #732 2014-12-05 18:11:49 +01:00
Christian Mehlmauer
a8c55ddee3 remove malware folder 2014-12-03 23:51:01 +01:00
Christian Mehlmauer
e080835224 updated zip 2014-12-03 23:45:33 +01:00
Christian Mehlmauer
2fe675abce remove malwares 2014-12-03 23:37:31 +01:00
ethicalhack3r
d230221999 Prevent parent theme infinite loop 2014-12-02 16:40:18 +01:00
ethicalhack3r
91a01265e5 Add response code to proxy error output 2014-12-01 11:00:19 +01:00
ethicalhack3r
77286301a7 Add protocol to sucuri url in banner 2014-11-27 21:33:54 +01:00
ethicalhack3r
7c39827c16 Add Sucuri sponsor to banner 2014-11-27 21:30:17 +01:00
Ryan Dewhurst
8f789994eb Merge pull request #729 from fgeek/rmobsoletecode
Remove obsolete code
2014-11-26 13:54:07 +01:00
Henri Salo
79cb9c8142 Remove obsolete code. If use cases are found from active installations these functions can be taken into use with proper unit tests. 2014-11-26 01:18:12 +02:00
erwanlr
de1d047c08 Adds the --version in the help - Fixes #716 2014-11-25 17:44:01 +01:00
erwanlr
8252cb486b Fixes #728 2014-11-25 17:36:11 +01:00
erwanlr
fb8ad72335 WpItems detection: Perform the passive check and filter only vulnerable results at the end if required, fixes #727 2014-11-21 16:49:04 +01:00
Christian Mehlmauer
bc4f0c002b added check for valid credentials 2014-11-19 10:50:04 +01:00
ethicalhack3r
0a53c52645 Add new updating info to docs #715 #691 2014-11-14 11:52:06 +01:00
ethicalhack3r
7941a8accb Update to Ruby 2.1.5 and travis 2014-11-13 17:02:21 +01:00
ethicalhack3r
5389923b34 Expand on readme.html finding output #717 2014-11-01 20:44:30 +01:00
ethicalhack3r
9c1149cb25 Update to Ruby 2.1.4 2014-10-27 14:42:03 +01:00
erwanlr
c5130de805 Merge pull request #711 from Soullivaneuh/db-updater-binary
Fix UTF-8 encode on security db file download
2014-10-20 15:19:57 +01:00
Sullivan SENECHAL
020633503b Fix UTF-8 encode on security db file download 2014-10-20 14:00:58 +02:00
Peter
74b9776801 Give warning if default username 'admin' is still used 2014-10-19 12:28:59 +02:00
ethicalhack3r
5a605d686c License amendment to make it more clear about value added usage 2014-10-13 10:09:04 +02:00
Christian Mehlmauer
4ba9bdf605 Fix #705 2014-10-03 23:07:15 +02:00
Christian Mehlmauer
3f647348c3 Update README.md 2014-10-01 22:02:00 +02:00
erwanlr
de4f90dd72 Adds v2.5.1 2014-09-29 13:49:06 +02:00
ethicalhack3r
4a7b4754f0 Fix wpvulndb url #700 2014-09-29 12:51:48 +02:00
ethicalhack3r
fe05534a95 Update wpstools docs #697 2014-09-27 16:55:11 +02:00
ethicalhack3r
c7c7e75b32 Fix for #698 2014-09-27 16:43:44 +02:00
Christian Mehlmauer
efc6aed388 Ruby 2.1.3 2014-09-27 00:27:19 +02:00
Peter
197521d5b1 Added wpvulndb.com URL 2014-09-26 15:01:44 +02:00
214 changed files with 9066 additions and 4169 deletions

21
.dockerignore Normal file
View File

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

31
.gitignore vendored
View File

@@ -1,14 +1,21 @@
cache
coverage
# 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
.bundle
.DS_Store
.DS_Store?
*.sublime-*
.idea
.*.swp
Gemfile.lock
log.txt
.yardoc
debug.log
wordlist.txt
rspec_results.html
.DS_Store?

View File

@@ -1 +1 @@
2.1.2
2.5.1

View File

@@ -1,15 +1,36 @@
language: ruby
sudo: false
cache: bundler
rvm:
- 1.9.2
- 1.9.3
- 2.0.0
- 2.1.0
- 2.1.1
- 2.1.2
script: bundle exec rspec
- 2.1.9
- 2.2.0
- 2.2.1
- 2.2.2
- 2.2.3
- 2.2.4
- 2.3.0
- 2.3.1
- 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"
- "gem regenerate_binstubs"
- "bundle --version"
before_script:
- "unzip -o $TRAVIS_BUILD_DIR/data.zip -d $HOME/.wpscan/"
script:
- "bundle exec rspec"
notifications:
email:
- wpscanteam@gmail.com
matrix:
allow_failures:
- rvm: 1.9.2
- team@wpscan.org
# do not build gh-pages branch
branches:
except:
- gh-pages

View File

@@ -1,6 +1,271 @@
# Changelog
## Master
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.5...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
* Updated dependencies and required ruby version
* Made some changes so wpscan works in ruby 2.4
* Added a Gemfile.lock to lock all dependencies
* You can now pass a wordlist from stdin via "--wordlist -"
* Improved version detection regexes
* Added an optional paramter to --log to specify a filename
WPScan Database Statistics:
* Total tracked wordpresses: 251
* Total tracked plugins: 68818
* Total tracked themes: 15132
* Total vulnerable wordpresses: 243
* Total vulnerable plugins: 1527
* Total vulnerable themes: 280
* Total wordpress vulnerabilities: 5263
* Total plugin vulnerabilities: 2406
* Total theme vulnerabilities: 349
## Version 2.9.2
Released: 2016-11-15
* Fixed error when detecting plugins with UTF-8 characters
* Use all possible finders to verify a detected version
* Fix error when detecting a WordPress version not in our database
* Added some additional clarification on error messages
* Upgrade terminal-table gem
* Add --cache-dir option
* Add --disable-tls-checks options
* Improve/add additional plugin passive detections
* Remove scripts when calculating page hashes
* Many other small bug fixes.
WPScan Database Statistics:
* Total tracked wordpresses: 194
* Total tracked plugins: 63703
* Total tracked themes: 13835
* Total vulnerable wordpresses: 177
* Total vulnerable plugins: 1382
* Total vulnerable themes: 379
* Total wordpress vulnerabilities: 2617
* Total plugin vulnerabilities: 2190
* Total theme vulnerabilities: 452
## Version 2.9.1
Released: 2016-05-06
* Update to Ruby 2.3.1, drop older ruby support
* New data file location
* Added experimental Windows support
* Display WordPress metadata on the detected version
* Several small fixes
WPScan Database Statistics:
* Total vulnerable versions: 156
* Total vulnerable plugins: 1324
* Total vulnerable themes: 376
* Total version vulnerabilities: 1998
* Total plugin vulnerabilities: 2057
* Total theme vulnerabilities: 449
## Version 2.9
Released: 2015-10-15
New
* GZIP Encoding in updater
* Adds --throttle option to throttle requests
* Uses new API and local database file structure
* Adds last updated and latest version to plugins and themes
Removed
* ArchAssault from README
* APIv1 local databases
General core
* Update to Ruby 2.2.3
* Use yajl-ruby as JSON parser
* New dependancy for Ubuntu 14.04 (libgmp-dev)
* Use Travis container based infra and caching
Fixed issues
* Fix #835 - Readme requests to wp root dir
* Fix #836 - Critical icon output twice when the site is not running WP
* Fix #839 - Terminal-table dependency is broken
* Fix #841 - error: undefined method `cells' for #<Array:0x000000029cc2f8>
* Fix #852 - GZIP Encoding in updater
* Fix #853 - APIv2 integration
* Fix #858 - Detection FP
* Fix #873 - false positive "site has Must Use Plugins"
WPScan Database Statistics:
* Total vulnerable versions: 132
* Total vulnerable plugins: 1170
* Total vulnerable themes: 368
* Total version vulnerabilities: 1476
* Total plugin vulnerabilities: 1913
* Total theme vulnerabilities: 450
## Version 2.8
Released: 2015-06-22
New
* Warn the user to update his DB files
* Added last db update to --version option (see #815)
* Add db checksum to verbose logging during update
* Option to hide banner
* Continue if user chooses not to update + db exists
* Don't update if user chooses default + no DBs exist
* Updates request timeout values to realistic ones (and in seconds)
Removed
* Removed `Time.parse('2000-01-01')` expedient
* Removed unnecessary 'return' and '()'
* Removed debug output
* Removed wpstools
General core
* Update to Ruby 2.2.2
* Switch to mitre
* Install bundler gem README
* Switch from gnutls to openssl
Fixed issues
* Fix #789 - Add blackarch to readme
* Fix #790 - Consider the target down after 30 requests timed out requests instead of 10
* Fix #791 - Rogue character causing the scan of non-wordpress site to crash
* Fix #792 - Adds the HttpError exception
* Fix #795 - Remove GHOST warning
* Fix #796 - Do not swallow exit code
* Fix #797 - Increases the timeout values
* Fix #801 - Forces UTF-8 encoding when enumerating usernames
* Fix #803 - Increases default connect-timeout to 10s
* Fix #804 - Updates the Theme detection pattern
* Fix #816 - Ignores potential non version chars in theme version detection
* Fix #819 - Removes potential spaces in robots.txt entries
WPScan Database Statistics:
* Total vulnerable versions: 98
* Total vulnerable plugins: 1076
* Total vulnerable themes: 361
* Total version vulnerabilities: 1104
* Total plugin vulnerabilities: 1763
* Total theme vulnerabilities: 443
## Version 2.7
Released: 2015-03-16
New
* Detects version in release date format
* Copyrights updated
* WP version detection from stylesheets
* New license
* Global HTTP request counter
* Add security-protection plugin detection
* Add GHOST warning if XMLRPC enabled
* Update databases from wpvulndb.com
* Enumerate usernames from WP <= 3.0 (thanks berotti3)
Removed
* README.txt
General core
* Update to Ruby 2.2.1
* Update to Ruby 2.2.0
* Add addressable gem
* Update Typhoeus gem to 0.7.0
* IDN support: encode non-ascii domain names (thanks dctabuyz)
* Improve page hash calculation (thanks dctabuyz)
* Version detection regex improved
Fixed issues
* Fix #745 - Plugin version pattern in readme.txt file not detected
* Fix #746 - Add a global counter for all active requests to server.
* Fix #747 - Add 'security-protection' plugin to wp_login_protection module
* Fix #753 - undefined method `round' for "10":String for request or connect timeouts
* Fix #760 - typhoeus issue (infinite loop)
WPScan Database Statistics:
* Total vulnerable versions: 89
* Total vulnerable plugins: 953
* Total vulnerable themes: 329
* Total version vulnerabilities: 1070
* Total plugin vulnerabilities: 1451
* Total theme vulnerabilities: 378
## Version 2.6
Released: 2014-12-19
New
* Updates the readmes to reflect the new --usernames option
* Improves plugin/theme version detection by looking at the "Version:"
* Solution to avoid mandatory blank newline at the end of the wordlist
* Add check for valid credentials
* Add Sucuri sponsor to banner
* Add protocol to sucuri url in banner
* Add response code to proxy error output
* Add a statement about mandatory newlines at the end of list
* Give warning if default username 'admin' is still used
* License amendment to make it more clear about value added usage
Removed
* remove malwares
* remove malware folder
* Removes the theme version check from the readme, unrealistic scenario
General core
* Update to Ruby 2.1.5 and travis
* Prevent parent theme infinite loop
* Fixes the progressbar being overriden by next brute forcing attempts
Fixed issues
* Fix UTF-8 encode on security db file download
* Fix #703 - Disable logging by default. Implement log option.
* Fix #705 - Installation instructions for Ubuntu < 14.04 apparently incomplete
* Fix #717 - Expand on readme.html finding output
* Fix #716 - Adds the --version in the help
* Fix #715 - Add new updating info to docs
* Fix #727 - WpItems detection: Perform the passive check and filter only vulnerable results at the end if required
* Fix #737 - Adds some readme files to check for plugin versions
* Fix #739 - Adds the --usernames option
WPScan Database Statistics:
* Total vulnerable versions: 88
* Total vulnerable plugins: 901
* Total vulnerable themes: 313
* Total version vulnerabilities: 1050
* Total plugin vulnerabilities: 1355
* Total theme vulnerabilities: 349
## Version 2.5.1
Released: 2014-09-29
Fixes reference URL to WPVDB
## Version 2.5
Released: 2014-09-26 (@ BruCON 2014)
@@ -30,7 +295,7 @@ General core
* Little output change and coloring
* Adds a missing verbose output
* Re-build redirection url if begin with slash '/'
* Fixes the remove_conditional_comments function
* Fixes the remove_conditional_comments function
* Ensures to give a string to Typhoeus
* Fix wpstools check-vuln-ref-urls
* Fix rspecs for new json
@@ -320,4 +585,3 @@ Fixed issues
## Version 2.1
Released 2013-3-4

21
CREDITS
View File

@@ -1,21 +0,0 @@
**CREDITS**
This file is to give credit to WPScan's contributors. If you feel your name should be in here, email ryandewhurst at gmail.
*WPScan Team*
Erwan.LR - @erwan_lr - (Project Developer)
Christian Mehlmauer - @_FireFart_ - (Project Developer)
Peter van der Laan - pvdl - (Vuln Hunter and Code Cleaner)
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 - Project Developer

37
Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
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
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
# 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"]

20
Gemfile
View File

@@ -1,14 +1,16 @@
source 'https://rubygems.org'
gem 'typhoeus', '~>0.6.8'
gem 'nokogiri'
gem 'json'
gem 'terminal-table'
gem 'ruby-progressbar', '>=1.6.0'
gem 'addressable', '>=2.5.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', '>=1.17.2'
gem 'simplecov'
gem 'rspec', '~>3.0'
gem 'rspec-its'
gem 'webmock', '>=2.3.2'
gem 'simplecov', '>=0.13.0'
gem 'rspec', '>=3.5.0'
gem 'rspec-its', '>=1.2.0'
end

71
Gemfile.lock Normal file
View File

@@ -0,0 +1,71 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
crack (0.4.3)
safe_yaml (~> 1.0.0)
diff-lcs (1.3)
docile (1.3.1)
ethon (0.11.0)
ffi (>= 1.3.0)
ffi (1.9.25)
hashdiff (0.3.7)
json (2.1.0)
mini_portile2 (2.3.0)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
public_suffix (3.0.2)
rspec (3.7.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-core (3.7.1)
rspec-support (~> 3.7.0)
rspec-expectations (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
rspec-its (1.2.0)
rspec-core (>= 3.0.0)
rspec-expectations (>= 3.0.0)
rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
rspec-support (3.7.1)
ruby-progressbar (1.9.0)
rubyzip (1.2.1)
safe_yaml (1.0.4)
simplecov (0.16.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
typhoeus (1.3.0)
ethon (>= 0.9.0)
unicode-display_width (1.4.0)
webmock (3.4.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
yajl-ruby (1.4.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)
rubyzip (>= 1.2.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.16.2

78
LICENSE
View File

@@ -1,20 +1,74 @@
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team.
WPScan Public Source License
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License.
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2018 WPScan Team.
Cases of commercialization are:
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
- Using WPScan to provide commercial managed/Software-as-a-Service services.
- Distributing WPScan as a commercial product or as part of one.
1. Definitions
Cases which do not require a commercial license, and thus fall under the terms of GNU General Public License, include (but are not limited to):
1.1 “License” means this document.
1.2 “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
1.3 “WPScan Team” means WPScans core developers.
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause.
- Using WPScan to test your own systems.
- Any non-commercial use of WPScan.
2. Commercialization
If you need to acquire a commercial license or are unsure about whether you need to acquire a commercial license, please get in touch, we will be happy to clarify things for you and work with you to accommodate your requirements.
A commercial use is one intended for commercial advantage or monetary compensation.
wpscanteam at gmail.com
Example cases of commercialization are:
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
- Using WPScan to provide commercial managed/Software-as-a-Service services.
- Distributing WPScan as a commercial product or as part of one.
- Using WPScan as a value added service/product.
Example cases which do not require a commercial license, and thus fall under the terms set out below, include (but are not limited to):
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit.
- Penetration Testing Linux Distributions including but not limited to Kali Linux, SamuraiWTF, BackBox Linux.
- Using WPScan to test your own systems.
- Any non-commercial use of WPScan.
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
Free-use Terms and Conditions;
3. Redistribution
Redistribution is permitted under the following conditions:
- Unmodified License is provided with WPScan.
- Unmodified Copyright notices are provided with WPScan.
- Does not conflict with the commercialization clause.
4. Copying
Copying is permitted so long as it does not conflict with the Redistribution clause.
5. Modification
Modification is permitted so long as it does not conflict with the Redistribution clause.
6. Contributions
Any Contributions assume the Contributor grants the WPScan Team the unlimited, non-exclusive right to reuse, modify and relicense the Contributor's content.
7. Support
WPScan is provided under an AS-IS basis and without any support, updates or maintenance. Support, updates and maintenance may be given according to the sole discretion of the WPScan Team.
8. Disclaimer of Warranty
WPScan is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the WPScan is free of defects, merchantable, fit for a particular purpose or non-infringing.
9. Limitation of Liability
To the extent permitted under Law, WPScan is provided under an AS-IS basis. The WPScan Team shall never, and without any limit, be liable for any damage, cost, expense or any other payment incurred as a result of WPScan's actions, failure, bugs and/or any other interaction between WPScan and end-equipment, computers, other software or any 3rd party, end-equipment, computer or services.
10. Disclaimer
Running WPScan against websites without prior mutual consent may be illegal in your country. The WPScan Team accept no liability and are not responsible for any misuse or damage caused by WPScan.
11. Trademark
The "wpscan" term is a registered trademark. This License does not grant the use of the "wpscan" trademark or the use of the WPScan logo.

293
README
View File

@@ -1,293 +0,0 @@
__________________________________________________
__ _______ _____
\ \ / / __ \ / ____|
\ \ /\ / /| |__) | (___ ___ __ _ _ __
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
\ /\ / | | ____) | (__| (_| | | | |
\/ \/ |_| |_____/ \___|\__,_|_| |_|
__________________________________________________
==LICENSE==
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team.
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License.
Cases of commercialization are:
- Using WPScan to provide commercial managed/Software-as-a-Service services.
- Distributing WPScan as a commercial product or as part of one.
Cases which do not require a commercial license, and thus fall under the terms of GNU General Public License, include (but are not limited to):
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause.
- Using WPScan to test your own systems.
- Any non-commercial use of WPScan.
If you need to acquire a commercial license or are unsure about whether you need to acquire a commercial license, please get in touch, we will be happy to clarify things for you and work with you to accommodate your requirements.
wpscanteam at gmail.com
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
==INSTALL==
WPScan comes pre-installed on the following Linux distributions:
* BackBox Linux
* Kali Linux
* Pentoo
* SamuraiWTF
* ArchAssault
Prerequisites:
* Windows not supported
* Ruby >= 1.9.2 - Recommended: 2.1.2
* Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
* RubyGems - Recommended: latest
* Git
-> Installing on Ubuntu:
Before Ubuntu 14.04:
sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
From Ubuntu 14.04:
sudo apt-get install libcurl4-gnutls-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && bundle install --without test
-> Installing on Debian:
sudo apt-get install git ruby ruby-dev libcurl4-gnutls-dev make
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler
bundle install --without test --path vendor/bundle
-> Installing on Fedora:
sudo yum install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && bundle install --without test
-> Installing on Archlinux:
pacman -Syu ruby
pacman -Syu libyaml
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && bundle install --without test
gem install typhoeus
gem install nokogiri
-> Installing on Mac OS X:
Apple Xcode, Command Line Tools and the libffi are needed (to be able to install the FFI gem), See http://stackoverflow.com/questions/17775115/cant-setup-ruby-environment-installing-fii-gem-error
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && sudo bundle install --without test
-> Installing with RVM:
cd ~
curl -sSL https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
rvm install 2.1.2
rvm use 2.1.2 --default
echo "gem: --no-ri --no-rdoc" > ~/.gemrc
gem install bundler
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
bundle install --without test
==KNOWN ISSUES==
- Typhoeus segmentation fault:
Update cURL to version => 7.21 (may have to install from source)
- Proxy not working:
Update cURL to version => 7.21.7 (may have to install from source).
Installation from sources :
- Grab the sources from http://curl.haxx.se/download.html
- Decompress the archive
- Open the folder with the extracted files
- Run ./configure
- Run make
- Run sudo make install
- Run sudo ldconfig
- cannot load such file -- readline:
Run sudo aptitude install libreadline5-dev libncurses5-dev
Then, open the directory of the readline gem (you have to locate it)
cd ~/.rvm/src/ruby-1.9.2-p180/ext/readline
ruby extconf.rb
make
make install
See http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/ for more details
- no such file to load -- rubygems
Run update-alternatives --config ruby
And select your ruby version
See https://github.com/wpscanteam/wpscan/issues/148
==WPSCAN ARGUMENTS==
--update Update the databases.
--url | -u <target url> The WordPress URL/domain to scan.
--force | -f Forces WPScan to not check if the remote site is running WordPress.
--enumerate | -e [option(s)] Enumeration.
option :
u usernames from id 1 to 10
u[10-20] usernames from id 10 to 20 (you must write [] chars)
p plugins
vp only vulnerable plugins
ap all plugins (can take a long time)
tt timthumbs
t themes
vt only vulnerable themes
at all themes (can take a long time)
Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
If no option is supplied, the default is "vt,tt,u,vp"
--exclude-content-based "<regexp or string>" Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double)
--config-file | -c <config file> Use the specified config file, see the example.conf.json
--user-agent | -a <User-Agent> Use the specified User-Agent
--random-agent | -r Use a random User-Agent
--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not
--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it. Subdirectories are allowed
--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory. If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed
--proxy <[protocol://]host:port> Supply a proxy (will override the one from conf/browser.conf.json).
HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported. If no protocol is given (format host:port), HTTP will be used
--proxy-auth <username:password> Supply the proxy login credentials.
--basic-auth <username:password> Set the HTTP Basic authentication.
--wordlist | -w <wordlist> Supply a wordlist for the password bruter and do the brute.
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
--username | -U <username> Only brute force the supplied username.
--cache-ttl <cache-ttl> Typhoeus cache TTL.
--request-timeout <request-timeout> Request Timeout.
--connect-timeout <connect-timeout> Connect Timeout.
--max-threads <max-threads> Maximum Threads.
--help | -h This help screen.
--verbose | -v Verbose output.
--batch Never ask for user input, use the default behaviour.
--no-color Do not use colors in the output.
==WPSCAN EXAMPLES==
Do 'non-intrusive' checks...
ruby wpscan.rb --url www.example.com
Do wordlist password brute force on enumerated users using 50 threads...
ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --threads 50
Do wordlist password brute force on the 'admin' username only...
ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --username admin
Enumerate installed plugins...
ruby wpscan.rb --url www.example.com --enumerate p
Run all enumeration tools...
ruby wpscan.rb --url www.example.com --enumerate
Use custom content directory...
ruby wpscan.rb -u www.example.com --wp-content-dir custom-content
Update WPScan's databases...
ruby wpscan.rb --update
Debug output...
ruby wpscan.rb --url www.example.com --debug-output 2>debug.log
==WPSTOOLS ARGUMENTS==
-v, --verbose Verbose output
--check-vuln-ref-urls, --cvru Check all the vulnerabilities reference urls for 404
--check-local-vulnerable-files, --clvf LOCAL_DIRECTORY Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells
--generate-plugin-list, --gpl [NUMBER_OF_ITEMS] Generate a new data/plugins.txt file. (supply number of *items* to parse, default : 1500)
--generate-full-plugin-list, --gfpl Generate a new full data/plugins.txt file
--generate-theme-list, --gtl [NUMBER_OF_ITEMS] Generate a new data/themes.txt file. (supply number of *items* to parse, default : 200)
--generate-full-theme-list, --gftl Generate a new full data/themes.txt file
--generate-all, --ga Generate a new full plugins, full themes, popular plugins and popular themes list
-s, --stats Show WpScan Database statistics
--spellcheck, --sc Check all files for common spelling mistakes.
==WPSTOOLS EXAMPLES==
- Generate a new 'most popular' plugin list, up to 1500 items ...
ruby wpstools.rb --generate-plugin-list 1500
Locally scan a wordpress installation for vulnerable files or shells:
ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/
Or check https://github.com/fgeek/pyfiscan project.
===PROJECT HOME===
www.wpscan.org
===REPOSITORY===
https://github.com/wpscanteam/wpscan
===ISSUES===
https://github.com/wpscanteam/wpscan/issues
===DEVELOPER DOCUMENTATION===
http://rdoc.info/github/wpscanteam/wpscan/frames
===SPONSOR===
WPScan is sponsored by the RandomStorm Open Source Initiative.
Visit RandomStorm at http://www.randomstorm.com

370
README.md
View File

@@ -1,31 +1,90 @@
![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.png?branch=master)](https://travis-ci.org/wpscanteam/wpscan)
#### LICENSE
[![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)
[![Docker Pulls](https://img.shields.io/docker/pulls/wpscanteam/wpscan.svg)](https://hub.docker.com/r/wpscanteam/wpscan/)
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team.
# LICENSE
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License.
## WPScan Public Source License
Cases of commercialization are:
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2018 WPScan Team.
- Using WPScan to provide commercial managed/Software-as-a-Service services.
- Distributing WPScan as a commercial product or as part of one.
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 which do not require a commercial license, and thus fall under the terms of GNU General Public License, include (but are not limited to):
### 1. Definitions
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause.
- Using WPScan to test your own systems.
- Any non-commercial use of WPScan.
1.1 "License" means this document.
If you need to acquire a commercial license or are unsure about whether you need to acquire a commercial license, please get in touch, we will be happy to clarify things for you and work with you to accommodate your requirements.
1.2 "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
wpscanteam at gmail.com
1.3 "WPScan Team" means WPScans core developers, an updated list of whom can be found within the CREDITS file.
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
### 2. Commercialization
#### INSTALL
A commercial use is one intended for commercial advantage or monetary compensation.
Example cases of commercialization are:
- Using WPScan to provide commercial managed/Software-as-a-Service services.
- Distributing WPScan as a commercial product or as part of one.
- Using WPScan as a value added service/product.
Example cases which do not require a commercial license, and thus fall under the terms set out below, include (but are not limited to):
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit.
- Penetration Testing Linux Distributions including but not limited to Kali Linux, SamuraiWTF, BackBox Linux.
- Using WPScan to test your own systems.
- Any non-commercial use of WPScan.
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
Free-use Terms and Conditions;
### 3. Redistribution
Redistribution is permitted under the following conditions:
- Unmodified License is provided with WPScan.
- Unmodified Copyright notices are provided with WPScan.
- Does not conflict with the commercialization clause.
### 4. Copying
Copying is permitted so long as it does not conflict with the Redistribution clause.
### 5. Modification
Modification is permitted so long as it does not conflict with the Redistribution clause.
### 6. Contributions
Any Contributions assume the Contributor grants the WPScan Team the unlimited, non-exclusive right to reuse, modify and relicense the Contributor's content.
### 7. Support
WPScan is provided under an AS-IS basis and without any support, updates or maintenance. Support, updates and maintenance may be given according to the sole discretion of the WPScan Team.
### 8. Disclaimer of Warranty
WPScan is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the WPScan is free of defects, merchantable, fit for a particular purpose or non-infringing.
### 9. Limitation of Liability
To the extent permitted under Law, WPScan is provided under an AS-IS basis. The WPScan Team shall never, and without any limit, be liable for any damage, cost, expense or any other payment incurred as a result of WPScan's actions, failure, bugs and/or any other interaction between WPScan and end-equipment, computers, other software or any 3rd party, end-equipment, computer or services.
### 10. Disclaimer
Running WPScan against websites without prior mutual consent may be illegal in your country. The WPScan Team accept no liability and are not responsible for any misuse or damage caused by WPScan.
### 11. Trademark
The "wpscan" term is a registered trademark. This License does not grant the use of the "wpscan" trademark or the use of the WPScan logo.
# INSTALL
WPScan comes pre-installed on the following Linux distributions:
@@ -33,111 +92,106 @@ WPScan comes pre-installed on the following Linux distributions:
- [Kali Linux](http://www.kali.org/)
- [Pentoo](http://www.pentoo.ch/)
- [SamuraiWTF](http://samurai.inguardians.com/)
- [ArchAssault](https://archassault.org/)
- [BlackArch](http://blackarch.org/)
Prerequisites:
On macOS WPScan is packaged by [Homebrew](https://brew.sh/) as [`wpscan`](http://braumeister.org/formula/wpscan).
- Ruby >= 1.9.2 - Recommended: 2.1.2
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
```
docker run -it --rm wpscanteam/wpscan -u https://yourblog.com [options]
```
For the available Options, please see https://github.com/wpscanteam/wpscan#wpscan-arguments
If you run the git version of wpscan we included some binstubs in ./bin for easier start of wpscan.
## Examples
Mount a local wordlist to the docker container and start a bruteforce attack for user 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
```
# the file must exist prior to starting the container, otherwise docker will create a directory with the filename
touch ~/FILENAME
docker run -it --rm -v ~/FILENAME:/wpscan/output.txt wpscanteam/wpscan --url https://yourblog.com --log /wpscan/output.txt
```
Published on https://hub.docker.com/r/wpscanteam/wpscan/
# Manual install
## Prerequisites
- Ruby >= 2.1.9 - Recommended: 2.5.1
- Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
- RubyGems - Recommended: latest
- Git
Windows is not supported.
### Installing dependencies on Ubuntu
####Installing on Ubuntu:
sudo apt-get install libcurl4-openssl-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential libgmp-dev zlib1g-dev
Before Ubuntu 14.04:
### Installing dependencies on Debian
sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
sudo apt-get install gcc git ruby ruby-dev libcurl4-openssl-dev make zlib1g-dev
From Ubuntu 14.04:
### Installing dependencies on Fedora
sudo apt-get install libcurl4-gnutls-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && bundle install --without test
sudo dnf install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel patch rpm-build
####Installing on Debian:
sudo apt-get install git ruby ruby-dev libcurl4-gnutls-dev make
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler
bundle install --without test --path vendor/bundle
####Installing on Fedora:
sudo yum install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && bundle install --without test
####Installing on Archlinux:
### Installing dependencies on Arch Linux
pacman -Syu ruby
pacman -Syu libyaml
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && bundle install --without test
gem install typhoeus
gem install nokogiri
####Installing on Mac OSX:
### Installing dependencies on macOS
Apple Xcode, Command Line Tools and the libffi are needed (to be able to install the FFI gem), See [http://stackoverflow.com/questions/17775115/cant-setup-ruby-environment-installing-fii-gem-error](http://stackoverflow.com/questions/17775115/cant-setup-ruby-environment-installing-fii-gem-error)
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && sudo bundle install --without test
## Installing with RVM (recommended when doing a manual install)
####Installing with RVM:
If you are using GNOME Terminal, there are some steps required before executing the commands. See here for more information:
https://rvm.io/integration/gnome-terminal#integrating-rvm-with-gnome-terminal
# Install all prerequisites for your OS (look above)
cd ~
curl -sSL https://rvm.io/mpapis.asc | gpg --import -
curl -sSL https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
rvm install 2.1.2
rvm use 2.1.2 --default
rvm install 2.5.1
rvm use 2.5.1 --default
echo "gem: --no-ri --no-rdoc" > ~/.gemrc
gem install bundler
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
gem install bundler
bundle install --without test
#### KNOWN ISSUES
## Installing manually (not recommended)
- Typhoeus segmentation fault
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && bundle install --without test
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-1.9.2-p180/ext/readline
ruby extconf.rb
make
make install
See [http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/](http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/) for more details
# KNOWN ISSUES
- no such file to load -- rubygems
@@ -147,15 +201,12 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
See [https://github.com/wpscanteam/wpscan/issues/148](https://github.com/wpscanteam/wpscan/issues/148)
#### WPSCAN ARGUMENTS
# WPSCAN ARGUMENTS
--update Update the databases.
--url | -u <target url> The WordPress URL/domain to scan.
--force | -f Forces WPScan to not check if the remote site is running WordPress.
--enumerate | -e [option(s)] Enumeration.
--update Update the database to the latest version.
--url | -u <target url> The WordPress URL/domain to scan.
--force | -f Forces WPScan to not check if the remote site is running WordPress.
--enumerate | -e [option(s)] Enumeration.
option :
u usernames from id 1 to 10
u[10-20] usernames from id 10 to 20 (you must write [] chars)
@@ -169,51 +220,44 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
If no option is supplied, the default is "vt,tt,u,vp"
--exclude-content-based "<regexp or string>" Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double)
--exclude-content-based "<regexp or string>"
Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied.
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).
--config-file | -c <config file> Use the specified config file, see the example.conf.json.
--user-agent | -a <User-Agent> Use the specified User-Agent.
--cookie <string> String to read cookies from.
--random-agent | -r Use a random User-Agent.
--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not
--batch Never ask for user input, use the default behaviour.
--no-color Do not use colors in the output.
--log [filename] Creates a log.txt file with WPScan's output if no filename is supplied. Otherwise the filename is used for logging.
--no-banner Prevents the WPScan banner from being displayed.
--disable-accept-header Prevents WPScan sending the Accept HTTP header.
--disable-referer Prevents setting the Referer header.
--disable-tls-checks Disables SSL/TLS certificate verification.
--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specify it.
Subdirectories are allowed.
--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory.
If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed
--proxy <[protocol://]host:port> Supply a proxy. HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported.
If no protocol is given (format host:port), HTTP will be used.
--proxy-auth <username:password> Supply the proxy login credentials.
--basic-auth <username:password> Set the HTTP Basic authentication.
--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.
If the "-" option is supplied, the wordlist is expected via STDIN.
--username | -U <username> Only brute force the supplied username.
--usernames <path-to-file> Only brute force the usernames from the file.
--cache-dir <cache-directory> Set the cache directory.
--cache-ttl <cache-ttl> Typhoeus cache TTL.
--request-timeout <request-timeout> Request Timeout.
--connect-timeout <connect-timeout> Connect Timeout.
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
--throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.
--help | -h This help screen.
--verbose | -v Verbose output.
--version Output the current version and exit.
--config-file | -c <config file> Use the specified config file, see the example.conf.json
--user-agent | -a <User-Agent> Use the specified User-Agent
--random-agent | -r Use a random User-Agent
--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not
--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it. Subdirectories are allowed
--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory. If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed
--proxy <[protocol://]host:port> Supply a proxy (will override the one from conf/browser.conf.json).
HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported. If no protocol is given (format host:port), HTTP will be used
--proxy-auth <username:password> Supply the proxy login credentials.
--basic-auth <username:password> Set the HTTP Basic authentication.
--wordlist | -w <wordlist> Supply a wordlist for the password bruter and do the brute.
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
--username | -U <username> Only brute force the supplied username.
--cache-ttl <cache-ttl> Typhoeus cache TTL.
--request-timeout <request-timeout> Request Timeout.
--connect-timeout <connect-timeout> Connect Timeout.
--max-threads <max-threads> Maximum Threads.
--help | -h This help screen.
--verbose | -v Verbose output.
--batch Never ask for user input, use the default behaviour.
--no-color Do not use colors in the output.
#### WPSCAN EXAMPLES
# WPSCAN EXAMPLES
Do 'non-intrusive' checks...
@@ -223,6 +267,10 @@ Do wordlist password brute force on enumerated users using 50 threads...
```ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --threads 50```
Do wordlist password brute force on enumerated users using STDIN as the wordlist...
```crunch 5 13 -f charset.lst mixalpha | ruby wpscan.rb --url www.example.com --wordlist -```
Do wordlist password brute force on the 'admin' username only...
```ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --username admin```
@@ -247,48 +295,22 @@ Debug output...
```ruby wpscan.rb --url www.example.com --debug-output 2>debug.log```
#### WPSTOOLS ARGUMENTS
-v, --verbose Verbose output
--check-vuln-ref-urls, --cvru Check all the vulnerabilities reference urls for 404
--check-local-vulnerable-files, --clvf LOCAL_DIRECTORY Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells
--generate-plugin-list, --gpl [NUMBER_OF_ITEMS] Generate a new data/plugins.txt file. (supply number of *items* to parse, default : 1500)
--generate-full-plugin-list, --gfpl Generate a new full data/plugins.txt file
--generate-theme-list, --gtl [NUMBER_OF_ITEMS] Generate a new data/themes.txt file. (supply number of *items* to parse, default : 200)
--generate-full-theme-list, --gftl Generate a new full data/themes.txt file
--generate-all, --ga Generate a new full plugins, full themes, popular plugins and popular themes list
-s, --stats Show WpScan Database statistics.
--spellcheck, --sc Check all files for common spelling mistakes.
#### WPSTOOLS EXAMPLES
Generate a new 'most popular' plugin list, up to 1500 items...
```ruby wpstools.rb --generate-plugin-list 1500```
Locally scan a wordpress installation for vulnerable files or shells:
```ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/```
Or check [pyfiscan](https://github.com/fgeek/pyfiscan) project.
#### PROJECT HOME
# PROJECT HOME
[http://www.wpscan.org](http://www.wpscan.org)
#### GIT REPOSITORY
# VULNERABILITY DATABASE
[https://wpvulndb.com](https://wpvulndb.com)
# GIT REPOSITORY
[https://github.com/wpscanteam/wpscan](https://github.com/wpscanteam/wpscan)
#### ISSUES
# ISSUES
[https://github.com/wpscanteam/wpscan/issues](https://github.com/wpscanteam/wpscan/issues)
#### DEVELOPER DOCUMENTATION
# DEVELOPER DOCUMENTATION
[http://rdoc.info/github/wpscanteam/wpscan/frames](http://rdoc.info/github/wpscanteam/wpscan/frames)
#### SPONSOR
WPScan is sponsored by the [RandomStorm](http://www.randomstorm.com) Open Source Initiative.

21
bin/rspec Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
cd $DIR/../
# always rebuild and include all GEMs
docker build --build-arg "BUNDLER_ARGS=--jobs=8" -t wpscan:rspec .
# update all gems (this updates Gemfile.lock on the host)
# this also needs some build dependencies
docker run --rm -u root -v $DIR/../Gemfile.lock:/wpscan/Gemfile.lock --entrypoint "" wpscan:rspec sh -c 'apk add --no-cache alpine-sdk ruby-dev libffi-dev zlib-dev && bundle update'
# rebuild image with latest GEMs
docker build --build-arg "BUNDLER_ARGS=--jobs=8" -t wpscan:rspec .
# run spec
docker run --rm -v $DIR/../:/wpscan --entrypoint "" wpscan:rspec rspec

12
bin/update_gems Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
cd $DIR/../
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"

14
bin/wpscan Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
cd $DIR/../
docker build -q -t wpscan:git .
docker run -it --rm wpscan:git "$@"

16
bin/wpscan-dev Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
cd $DIR/../
if [[ -n "$WPSCAN_BUILD" ]]; then
docker build -q -t wpscan:git .
fi
docker run -it --rm -v $DIR/../:/wpscan wpscan:git "$@"

BIN
data.zip

Binary file not shown.

2
data/.gitignore vendored
View File

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

View File

@@ -23,7 +23,7 @@ end
html = open(html_path).read
examples = html.match(/(\d+) examples/)[0].to_i rescue 0
errors = html.match(/(\d+) errors/)[0].to_i rescue 0
if errors == 0 then
if errors == 0
errors = html.match(/(\d+) failure/)[0].to_i rescue 0
end
pending = html.match(/(\d+) pending/)[0].to_i rescue 0

19
dev/stats.rb Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env ruby
# encoding: UTF-8
require File.expand_path(File.join(__dir__, '..', 'lib', 'wpscan', 'wpscan_helper'))
wordpress_json = json(WORDPRESSES_FILE)
plugins_json = json(PLUGINS_FILE)
themes_json = json(THEMES_FILE)
puts 'WPScan Database Statistics:'
puts "* Total tracked wordpresses: #{wordpress_json.count}"
puts "* Total tracked plugins: #{plugins_json.count}"
puts "* Total tracked themes: #{themes_json.count}"
puts "* Total vulnerable wordpresses: #{wordpress_json.select { |item| !wordpress_json[item]['vulnerabilities'].empty? }.count}"
puts "* Total vulnerable plugins: #{plugins_json.select { |item| !plugins_json[item]['vulnerabilities'].empty? }.count}"
puts "* Total vulnerable themes: #{themes_json.select { |item| !themes_json[item]['vulnerabilities'].empty? }.count}"
puts "* Total wordpress vulnerabilities: #{wordpress_json.map {|k,v| v['vulnerabilities'].count}.inject(:+)}"
puts "* Total plugin vulnerabilities: #{plugins_json.map {|k,v| v['vulnerabilities'].count}.inject(:+)}"
puts "* Total theme vulnerabilities: #{themes_json.map {|k,v| v['vulnerabilities'].count}.inject(:+)}"

View File

@@ -2,17 +2,17 @@
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0",
/* Uncomment the "proxy" line to use the proxy
SOCKS proxies (4, 4A, 5) are supported, ie : "proxy": "socks5://127.0.0.1:9000"
If you do not specify the protocol, http will be used
SOCKS proxies (4, 4A, 5) are supported, ie : "proxy": "socks5://127.0.0.1:9000"
If you do not specify the protocol, http will be used
*/
//"proxy": "127.0.0.1:3128",
//"proxy_auth": "username:password",
"cache_ttl": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
"request_timeout": 2000, // 2s
"request_timeout": 60, // 1min
"connect_timeout": 1000, // 1s
"connect_timeout": 10, // 10s
"max_threads": 20
}

View File

@@ -17,20 +17,24 @@ class Browser
:proxy_auth,
:request_timeout,
:connect_timeout,
:cookie
:cookie,
:throttle,
:disable_accept_header,
:disable_referer,
:disable_tls_checks
]
@@instance = nil
attr_reader :hydra, :cache_dir
attr_accessor :referer, :cookie
attr_accessor :referer, :cookie, :vhost
# @param [ Hash ] options
#
# @return [ Browser ]
def initialize(options = {})
@cache_dir = options[:cache_dir] || CACHE_DIR + '/browser'
@cache_dir = options[:cache_dir] || CACHE_DIR + '/browser'
# sets browser defaults
browser_defaults
@@ -66,18 +70,24 @@ class Browser
@@instance = nil
end
# Override for setting the User-Agent
# @param [ String ] user_agent
def user_agent=(user_agent)
Typhoeus::Config.user_agent = user_agent
end
#
# sets browser default values
#
def browser_defaults
@max_threads = 20
# 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
@cache_ttl = 600
# 2s
@request_timeout = 2000
# 1s
@connect_timeout = 1000
@user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
Typhoeus::Config.user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
@max_threads = 20
# 10 minutes, at this time the cache is cleaned before each scan.
# If this value is set to 0, the cache will be disabled
@cache_ttl = 600
@request_timeout = 60 # 60s
@connect_timeout = 10 # 10s
@throttle = 0
end
#
@@ -88,7 +98,6 @@ class Browser
#
# @return [ void ]
def load_config(config_file = nil)
if File.symlink?(config_file)
raise '[ERROR] Config file is a symlink.'
else
@@ -101,7 +110,6 @@ class Browser
self.send(:"#{option_name}=", data[option_name])
end
end
end
# @param [ String ] url
@@ -116,18 +124,9 @@ class Browser
#
# @return [ Hash ]
def merge_request_params(params = {})
params = Browser.append_params_header_field(
params,
'User-Agent',
@user_agent
)
if @proxy
params = params.merge(proxy: @proxy)
if @proxy_auth
params = params.merge(proxyauth: @proxy_auth)
end
params.merge!(proxy: @proxy)
params.merge!(proxyuserpwd: @proxy_auth) if @proxy_auth
end
if @basic_auth
@@ -138,23 +137,37 @@ class Browser
)
end
if vhost
params = Browser.append_params_header_field(
params,
'Host',
vhost
)
end
params.merge!(referer: referer)
params.merge!(timeout: @request_timeout) if @request_timeout
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout
params.merge!(timeout: @request_timeout) if @request_timeout && !params.key?(:timeout)
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout && !params.key?(:connecttimeout)
# Used to enable the cache system if :cache_ttl > 0
params.merge!(cache_ttl: @cache_ttl) unless params.has_key?(:cache_ttl)
params.merge!(cache_ttl: @cache_ttl) unless params.key?(:cache_ttl)
# Prevent infinite self redirection
params.merge!(maxredirs: 3) unless params.has_key?(:maxredirs)
params.merge!(maxredirs: 3) unless params.key?(:maxredirs)
# Disable SSL-Certificate checks
params.merge!(ssl_verifypeer: false)
params.merge!(ssl_verifyhost: 0)
if @disable_tls_checks
# Cert validity check
params.merge!(ssl_verifypeer: 0) unless params.key?(:ssl_verifypeer)
# Cert hostname check
params.merge!(ssl_verifyhost: 0) unless params.key?(:ssl_verifyhost)
end
params.merge!(cookiejar: @cache_dir + '/cookie-jar')
params.merge!(cookiefile: @cache_dir + '/cookie-jar')
params.merge!(cookie: @cookie) if @cookie
params = Browser.remove_params_header_field(params, 'Accept') if @disable_accept_header
params = Browser.remove_params_header_field(params, 'Referer') if @disable_referer
params
end
@@ -175,4 +188,17 @@ class Browser
params
end
# @param [ Hash ] params
# @param [ String ] field
# @param [ Mixed ] field_value
#
# @return [ Array ]
def self.remove_params_header_field(params = {}, field)
if !params.has_key?(:headers)
params = params.merge(:headers => { field => nil })
elsif !params[:headers].has_key?(field)
params[:headers][field] = nil
end
params
end
end

View File

@@ -3,9 +3,8 @@
class Browser
module Options
attr_accessor :cache_ttl, :request_timeout, :connect_timeout
attr_reader :basic_auth, :proxy, :proxy_auth
attr_writer :user_agent
attr_accessor :request_timeout, :connect_timeout, :user_agent, :disable_accept_header, :disable_referer, :disable_tls_checks
attr_reader :basic_auth, :cache_ttl, :proxy, :proxy_auth, :throttle
# Sets the Basic Authentification credentials
# Accepted format:
@@ -21,10 +20,14 @@ class Browser
elsif auth =~ /\ABasic [a-zA-Z0-9=]+\z/
@basic_auth = auth
else
raise 'Invalid basic authentication format, "login:password" or "Basic base_64_encoded" expected'
raise "Invalid basic authentication format, \"login:password\" or \"Basic base_64_encoded\" expected. Your input: #{auth}"
end
end
def cache_ttl=(ttl)
@cache_ttl = ttl.to_i
end
# @return [ Integer ]
def max_threads
@max_threads || 1
@@ -82,7 +85,7 @@ class Browser
#
# @return [ void ]
def request_timeout=(timeout)
@request_timeout = timeout
@request_timeout = timeout.to_i
end
# Sets the connect timeout
@@ -90,7 +93,12 @@ class Browser
#
# @return [ void ]
def connect_timeout=(timeout)
@connect_timeout = timeout
@connect_timeout = timeout.to_i
end
# @param [ String, Integer ] throttle
def throttle=(throttle)
@throttle = throttle.to_i.abs / 1000.0
end
protected
@@ -110,6 +118,5 @@ class Browser
end
end
end
end
end

View File

@@ -9,39 +9,51 @@
#
require 'yaml'
require 'fileutils'
class CacheFileStore
attr_reader :storage_path, :serializer
attr_reader :storage_path, :cache_dir, :serializer
# The serializer must have the 2 methods .load and .dump
# (Marshal and YAML have them)
# YAML is Human Readable, contrary to Marshal which store in a binary format
# Marshal does not need any "require"
def initialize(storage_path, serializer = Marshal)
@cache_dir = File.expand_path(storage_path)
@storage_path = File.expand_path(File.join(storage_path, storage_dir))
@serializer = serializer
# File.directory? for ruby <= 1.9 otherwise,
# it makes more sense to do Dir.exist? :/
unless File.directory?(@storage_path)
unless Dir.exist?(@storage_path)
FileUtils.mkdir_p(@storage_path)
end
unless Pathname.new(@storage_path).writable?
fail "#{@storage_path} is not writable"
end
end
def clean
Dir[File.join(@storage_path, '*')].each do |f|
File.delete(f) unless File.symlink?(f)
# clean old directories
Dir[File.join(@cache_dir, '*')].each do |f|
if File.directory?(f)
# delete directory if create time is older than 4 hours
FileUtils.rm_rf(f) if File.mtime(f) < (Time.now - (60*240))
else
File.delete(f) unless File.symlink?(f)
end
end
end
def read_entry(key)
@serializer.load(File.read(get_entry_file_path(key)))
rescue
nil
begin
@serializer.load(File.read(get_entry_file_path(key)))
rescue
nil
end
end
def write_entry(key, data_to_store, cache_ttl)
if cache_ttl > 0
if cache_ttl && cache_ttl > 0
File.open(get_entry_file_path(key), 'w') do |f|
begin
f.write(@serializer.dump(data_to_store))

149
lib/common/collections/wp_items.rb Executable file → Normal file
View File

@@ -1,74 +1,75 @@
# encoding: UTF-8
require 'common/collections/wp_items/detectable'
require 'common/collections/wp_items/output'
class WpItems < Array
extend WpItems::Detectable
include WpItems::Output
attr_accessor :wp_target
# @param [ WpTarget ] wp_target
def initialize(wp_target = nil)
self.wp_target = wp_target
end
# @param [String,] argv
#
# @return [ void ]
def add(*args)
index = 0
until args[index].nil?
arg = args[index]
if arg.is_a?(String)
if (next_arg = args[index + 1]).is_a?(Hash)
item = create_item(arg, next_arg)
index += 1
else
item = create_item(arg)
end
elsif arg.is_a?(Item)
item = arg
else
raise 'Invalid arguments'
end
self << item
index += 1
end
end
# @param [ String ] name
# @param [ Hash ] attrs
#
# @return [ WpItem ]
def create_item(name, attrs = {})
raise 'wp_target must be set' unless wp_target
item_class.new(
wp_target.uri,
attrs.merge(
name: name,
wp_content_dir: wp_target.wp_content_dir,
wp_plugins_dir: wp_target.wp_plugins_dir
) { |key, oldval, newval| oldval }
)
end
# @param [ WpItems ] other
#
# @return [ self ]
def +(other)
other.each { |item| self << item }
self
end
protected
# @return [ Class ]
def item_class
Object.const_get(self.class.to_s.gsub(/.$/, ''))
end
end
# encoding: UTF-8
require 'common/collections/wp_items/detectable'
require 'common/collections/wp_items/output'
class WpItems < Array
extend WpItems::Detectable
include WpItems::Output
attr_accessor :wp_target
# @param [ WpTarget ] wp_target
def initialize(wp_target = nil)
self.wp_target = wp_target
end
# @param [String] args
#
# @return [ void ]
def add(*args)
index = 0
until args[index].nil?
arg = args[index]
if arg.is_a?(String)
if (next_arg = args[index + 1]).is_a?(Hash)
item = create_item(arg, next_arg)
index += 1
else
item = create_item(arg)
end
elsif arg.is_a?(Item)
item = arg
else
raise 'Invalid arguments'
end
self << item
index += 1
end
end
# @param [ String ] name
# @param [ Hash ] attrs
#
# @return [ WpItem ]
def create_item(name, attrs = {})
raise 'wp_target must be set' unless wp_target
item_class.new(
wp_target.uri,
attrs.merge(
name: name,
wp_content_dir: wp_target.wp_content_dir,
wp_plugins_dir: wp_target.wp_plugins_dir
) { |key, oldval, newval| oldval }
)
end
# @param [ WpItems ] other
#
# @return [ self ]
def +(other)
other.each { |item| self << item }
self
end
protected
# @return [ Class ]
def item_class
Object.const_get(self.class.to_s.gsub(/.$/, ''))
end
end

478
lib/common/collections/wp_items/detectable.rb Executable file → Normal file
View File

@@ -1,238 +1,240 @@
# encoding: UTF-8
class WpItems < Array
module Detectable
attr_reader :vulns_file, :item_xpath
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
# @option options [ Boolean ] :show_progression Whether or not output the progress bar
# @option options [ Boolean ] :only_vulnerable Only check for vulnerable items
# @option options [ String ] :exclude_content
#
# @return [ WpItems ]
def aggressive_detection(wp_target, options = {})
browser = Browser.instance
hydra = browser.hydra
targets = targets_items(wp_target, options)
progress_bar = progress_bar(targets.size, options)
queue_count = 0
exist_options = {
error_404_hash: wp_target.error_404_hash,
homepage_hash: wp_target.homepage_hash,
exclude_content: options[:exclude_content] ? %r{#{options[:exclude_content]}} : nil
}
# If we only want the vulnerable ones, the passive detection is ignored
# Otherwise, a passive detection is performed, and results will be merged
results = options[:only_vulnerable] ? new : passive_detection(wp_target, options)
targets.each do |target_item|
request = browser.forge_request(target_item.url, request_params)
request.on_complete do |response|
progress_bar.progress += 1 if options[:show_progression]
if target_item.exists?(exist_options, response)
if !results.include?(target_item)
if !options[:only_vulnerable] || options[:only_vulnerable] && target_item.vulnerable?
results << target_item
end
end
end
end
hydra.queue(request)
queue_count += 1
if queue_count >= browser.max_threads
hydra.run
queue_count = 0
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
end
end
# run the remaining requests
hydra.run
results.sort!
results # can't just return results.sort because the #sort returns an array, and we want a WpItems
end
# @param [ Integer ] targets_size
# @param [ Hash ] options
#
# @return [ ProgressBar ]
# :nocov:
def progress_bar(targets_size, options)
if options[:show_progression]
ProgressBar.create(
format: '%t %a <%B> (%c / %C) %P%% %e',
title: ' ', # Used to craete a left margin
total: targets_size
)
end
end
# :nocov:
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
#
# @return [ WpItems ]
def passive_detection(wp_target, options = {})
results = new(wp_target)
# improves speed
body = remove_base64_images_from_html(Browser.get(wp_target.url).body)
page = Nokogiri::HTML(body)
names = []
page.css('link,script,style').each do |tag|
%w(href src).each do |attribute|
attr_value = tag.attribute(attribute).to_s
next unless attr_value
names << Regexp.last_match[1] if attr_value.match(attribute_pattern(wp_target))
end
next unless tag.name == 'script' || tag.name == 'style'
code = tag.text.to_s
next if code.empty?
code.scan(code_pattern(wp_target)).flatten.uniq.each do |item_name|
names << item_name
end
end
names.uniq.each { |name| results.add(name) }
results.sort!
results
end
protected
# @param [ WpTarget ] wp_target
#
# @return [ Regex ]
def item_pattern(wp_target)
type = to_s.gsub(/Wp/, '').downcase
wp_content_dir = wp_target.wp_content_dir
wp_content_url = wp_target.uri.merge(wp_content_dir).to_s
url = /#{wp_content_url.gsub(%r{\A(?:http|https)}, 'https?').gsub('/', '\\\\\?\/')}/i
content_dir = %r{(?:#{url}|\\?\/\\?\/?#{wp_content_dir})}i
%r{#{content_dir}\\?/#{type}\\?/}
end
# @param [ WpTarget ] wp_target
#
# @return [ Regex ]
def attribute_pattern(wp_target)
/\A#{item_pattern(wp_target)}([^\/]+)/i
end
# @param [ WpTarget ] wp_target
#
# @return [ Regex ]
def code_pattern(wp_target)
/["'\(]#{item_pattern(wp_target)}([^\\\/\)"']+)/i
end
# The default request parameters
#
# @return [ Hash ]
def request_params; { cache_ttl: 0, followlocation: true } end
# @param [ WpTarget ] wp_target
# @param [ options ] options
# @option options [ Boolean ] :only_vulnerable
# @option options [ String ] :file The path to the file containing the targets
#
# @return [ Array<WpItem> ]
def targets_items(wp_target, options = {})
item_class = self.item_class
vulns_file = self.vulns_file
targets = vulnerable_targets_items(wp_target, item_class, vulns_file)
unless options[:only_vulnerable]
unless options[:file]
raise 'A file must be supplied'
end
targets += targets_items_from_file(options[:file], wp_target, item_class, vulns_file)
end
targets.uniq! { |t| t.name }
targets.sort_by { rand }
end
# @param [ WpTarget ] wp_target
# @param [ Class ] item_class
# @param [ String ] vulns_file
#
# @return [ Array<WpItem> ]
def vulnerable_targets_items(wp_target, item_class, vulns_file)
targets = []
json = json(vulns_file)
[*json].each do |item|
targets << create_item(
item_class,
item.keys.inject,
wp_target,
vulns_file
)
end
targets
end
# @param [ Class ] klass
# @param [ String ] name
# @param [ WpTarget ] wp_target
# @option [ String ] vulns_file
#
# @return [ WpItem ]
def create_item(klass, name, wp_target, vulns_file = nil)
klass.new(
wp_target.uri,
name: name,
vulns_file: vulns_file,
wp_content_dir: wp_target.wp_content_dir,
wp_plugins_dir: wp_target.wp_plugins_dir
)
end
# @param [ String ] file
# @param [ WpTarget ] wp_target
# @param [ Class ] item_class
# @param [ String ] vulns_file
#
# @return [ Array<WpItem> ]
def targets_items_from_file(file, wp_target, item_class, vulns_file)
targets = []
File.open(file, 'r') do |f|
f.readlines.collect do |item_name|
targets << create_item(
item_class,
item_name.strip,
wp_target,
vulns_file
)
end
end
targets
end
# @return [ Class ]
def item_class
Object.const_get(self.to_s.gsub(/.$/, ''))
end
end
end
# encoding: UTF-8
class WpItems < Array
module Detectable
attr_reader :vulns_file, :item_xpath
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
# @option options [ Boolean ] :show_progression Whether or not output the progress bar
# @option options [ Boolean ] :only_vulnerable Only check for vulnerable items
# @option options [ String ] :exclude_content
#
# @return [ WpItems ]
def aggressive_detection(wp_target, options = {})
browser = Browser.instance
hydra = browser.hydra
targets = targets_items(wp_target, options)
progress_bar = progress_bar(targets.size, options)
queue_count = 0
exist_options = {
error_404_hash: wp_target.error_404_hash,
homepage_hash: wp_target.homepage_hash,
exclude_content: options[:exclude_content] ? %r{#{options[:exclude_content]}} : nil
}
results = passive_detection(wp_target, options)
targets.each do |target_item|
request = browser.forge_request(target_item.url, request_params)
request.on_complete do |response|
progress_bar.progress += 1 if options[:show_progression]
if target_item.exists?(exist_options, response)
results << target_item unless results.include?(target_item)
end
end
hydra.queue(request)
queue_count += 1
if queue_count >= browser.max_threads
hydra.run
queue_count = 0
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
end
end
# run the remaining requests
hydra.run
results.select!(&:vulnerable?) if options[:type] == :vulnerable
results.sort!
results # can't just return results.sort as it would return an array, and we want a WpItems
end
# @param [ Integer ] targets_size
# @param [ Hash ] options
#
# @return [ ProgressBar ]
# :nocov:
def progress_bar(targets_size, options)
if options[:show_progression]
ProgressBar.create(
format: '%t %a <%B> (%c / %C) %P%% %e',
title: ' ', # Used to craete a left margin
total: targets_size
)
end
end
# :nocov:
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
#
# @return [ WpItems ]
def passive_detection(wp_target, options = {})
results = new(wp_target)
# improves speed
body = remove_base64_images_from_html(Browser.get(wp_target.url).body)
page = Nokogiri::HTML(body)
names = []
page.css('link,script,style').each do |tag|
%w(href src).each do |attribute|
attr_value = tag.attribute(attribute).to_s
next unless attr_value
names << Regexp.last_match[1] if attr_value.match(attribute_pattern(wp_target))
end
next unless tag.name == 'script' || tag.name == 'style'
code = tag.text.to_s
next if code.empty?
if !code.valid_encoding?
code = code.encode('UTF-16be', :invalid => :replace, :replace => '?').encode('UTF-8')
end
code.scan(code_pattern(wp_target)).flatten.uniq.each do |item_name|
names << item_name
end
end
names.uniq.each { |name| results.add(name) }
results.sort!
results
end
protected
# @param [ WpTarget ] wp_target
#
# @return [ Regex ]
def item_pattern(wp_target)
type = to_s.gsub(/Wp/, '').downcase
wp_content_dir = wp_target.wp_content_dir
wp_content_url = wp_target.uri.merge(wp_content_dir).to_s
url = wp_content_url.gsub(%r{\A(?:http|https)://}, '(?:https?:)?//').gsub('/', '\\\\\?\/')
content_dir = %r{(?:#{url}|\\?\/\\?\/?#{wp_content_dir})}i
%r{#{content_dir}\\?/#{type}\\?/}
end
# @param [ WpTarget ] wp_target
#
# @return [ Regex ]
def attribute_pattern(wp_target)
/\A#{item_pattern(wp_target)}([^\/]+)/i
end
# @param [ WpTarget ] wp_target
#
# @return [ Regex ]
def code_pattern(wp_target)
/["'\(]#{item_pattern(wp_target)}([^\\\/\)"']+)/i
end
# The default request parameters
#
# @return [ Hash ]
def request_params; { cache_ttl: 0, followlocation: true } end
# @param [ WpTarget ] wp_target
# @param [ options ] options
# @option options [ Boolean ] :only_vulnerable
# @option options [ String ] :file The path to the file containing the targets
#
# @return [ Array<WpItem> ]
def targets_items(wp_target, options = {})
item_class = self.item_class
vulns_file = self.vulns_file
targets = target_items_from_type(wp_target, item_class, vulns_file, options[:type])
targets.uniq! { |t| t.name }
targets.sort_by { rand }
end
# @param [ WpTarget ] wp_target
# @param [ Class ] item_class
# @param [ String ] vulns_file
#
# @return [ Array<WpItem> ]
def target_items_from_type(wp_target, item_class, vulns_file, type)
targets = []
json = json(vulns_file)
case type
when :vulnerable
items = json.select { |item| !json[item]['vulnerabilities'].empty? }.keys
when :popular
items = json.select { |item| json[item]['popular'] == true }.keys
when :all
items = json.keys
else
raise "Unknown type #{type}"
end
items.each do |item|
targets << create_item(
item_class,
item,
wp_target,
vulns_file
)
end
targets
end
# @param [ Class ] klass
# @param [ String ] name
# @param [ WpTarget ] wp_target
# @option [ String ] vulns_file
#
# @return [ WpItem ]
def create_item(klass, name, wp_target, vulns_file = nil)
klass.new(
wp_target.uri,
name: name,
vulns_file: vulns_file,
wp_content_dir: wp_target.wp_content_dir,
wp_plugins_dir: wp_target.wp_plugins_dir
)
end
# @param [ String ] file
# @param [ WpTarget ] wp_target
# @param [ Class ] item_class
# @param [ String ] vulns_file
#
# @return [ Array<WpItem> ]
def targets_items_from_file(file, wp_target, item_class, vulns_file)
targets = []
File.open(file, 'r') do |f|
f.readlines.collect do |item_name|
targets << create_item(
item_class,
item_name.strip,
wp_target,
vulns_file
)
end
end
targets
end
# @return [ Class ]
def item_class
Object.const_get(self.to_s.gsub(/.$/, ''))
end
end
end

16
lib/common/collections/wp_plugins.rb Executable file → Normal file
View File

@@ -1,8 +1,8 @@
# encoding: UTF-8
require 'common/collections/wp_plugins/detectable'
class WpPlugins < WpItems
extend WpPlugins::Detectable
end
# encoding: UTF-8
require 'common/collections/wp_plugins/detectable'
class WpPlugins < WpItems
extend WpPlugins::Detectable
end

View File

@@ -2,17 +2,11 @@
class WpPlugins < WpItems
module Detectable
# @return [ String ]
def vulns_file
PLUGINS_VULNS_FILE
PLUGINS_FILE
end
# @return [ String ]
# def item_xpath
# '//plugin'
# end
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
#
@@ -68,10 +62,14 @@ class WpPlugins < WpItems
wp_plugins.add('all-in-one-seo-pack', version: $1)
end
if body =~ /<!-- This site is optimized with the Yoast WordPress SEO plugin v([^\s]+) -/i
if body =~ /<!-- This site is optimized with the Yoast (?:WordPress )?SEO plugin v([^\s]+) -/i
wp_plugins.add('wordpress-seo', version: $1)
end
if body =~ /<!-- Google Universal Analytics for WordPress v([^\s]+) -/i
wp_plugins.add('google-universal-analytics', version: $1)
end
wp_plugins
end

16
lib/common/collections/wp_themes.rb Executable file → Normal file
View File

@@ -1,8 +1,8 @@
# encoding: UTF-8
require 'common/collections/wp_themes/detectable'
class WpThemes < WpItems
extend WpThemes::Detectable
end
# encoding: UTF-8
require 'common/collections/wp_themes/detectable'
class WpThemes < WpItems
extend WpThemes::Detectable
end

View File

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

16
lib/common/collections/wp_timthumbs.rb Executable file → Normal file
View File

@@ -1,8 +1,8 @@
# encoding: UTF-8
require 'common/collections/wp_timthumbs/detectable'
class WpTimthumbs < WpItems
extend WpTimthumbs::Detectable
end
# encoding: UTF-8
require 'common/collections/wp_timthumbs/detectable'
class WpTimthumbs < WpItems
extend WpTimthumbs::Detectable
end

View File

@@ -41,7 +41,7 @@ class WpTimthumbs < WpItems
%w{
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
scripts/timthumb.php tools/timthumb.php functions/timthumb.php thumb.php
}.each do |path|
wp_timthumb.path = "$wp-content$/themes/#{theme_name}/#{path}"

22
lib/common/collections/wp_users.rb Executable file → Normal file
View File

@@ -1,11 +1,11 @@
# encoding: UTF-8
require 'common/collections/wp_users/detectable'
require 'common/collections/wp_users/output'
require 'common/collections/wp_users/brute_forcable'
class WpUsers < WpItems
extend WpUsers::Detectable
include WpUsers::Output
include WpUsers::BruteForcable
end
# encoding: UTF-8
require 'common/collections/wp_users/detectable'
require 'common/collections/wp_users/output'
require 'common/collections/wp_users/brute_forcable'
class WpUsers < WpItems
extend WpUsers::Detectable
include WpUsers::Output
include WpUsers::BruteForcable
end

68
lib/common/collections/wp_users/detectable.rb Executable file → Normal file
View File

@@ -1,34 +1,34 @@
# encoding: UTF-8
class WpUsers < WpItems
module Detectable
# @return [ Hash ]
def request_params; {} end
# No passive detection
#
# @return [ WpUsers ]
def passive_detection(wp_target, options = {})
new
end
protected
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
# @option options [ Range ] :range ((1..10))
#
# @return [ Array<WpUser> ]
def targets_items(wp_target, options = {})
range = options[:range] || (1..10)
targets = []
range.each do |user_id|
targets << WpUser.new(wp_target.uri, id: user_id)
end
targets
end
end
end
# encoding: UTF-8
class WpUsers < WpItems
module Detectable
# @return [ Hash ]
def request_params; {} end
# No passive detection
#
# @return [ WpUsers ]
def passive_detection(wp_target, options = {})
new
end
protected
# @param [ WpTarget ] wp_target
# @param [ Hash ] options
# @option options [ Range ] :range ((1..10))
#
# @return [ Array<WpUser> ]
def targets_items(wp_target, options = {})
range = options[:range] || (1..10)
targets = []
range.each do |user_id|
targets << WpUser.new(wp_target.uri, id: user_id)
end
targets
end
end
end

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
@@ -38,6 +38,7 @@ class WpUsers < WpItems
junk = get_equal_string_end(display_names)
unless junk.nil? or junk.empty?
self.each do |u|
u.display_name ||= ''
u.display_name = u.display_name.sub(/#{Regexp.escape(junk)}$/, '')
end
end

View File

@@ -1,40 +1,34 @@
# encoding: UTF-8
LIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
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')
WPSTOOLS_LIB_DIR = File.join(LIB_DIR, 'wpstools')
UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater')
COMMON_LIB_DIR = File.join(LIB_DIR, 'common')
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models')
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) # ~/
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
WPSTOOLS_PLUGINS_DIR = File.join(WPSTOOLS_LIB_DIR, 'plugins')
# 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
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.txt')
PLUGINS_FULL_FILE = File.join(DATA_DIR, 'plugins_full.txt')
PLUGINS_VULNS_FILE = File.join(DATA_DIR, 'plugin_vulns.json')
THEMES_FILE = File.join(DATA_DIR, 'themes.txt')
THEMES_FULL_FILE = File.join(DATA_DIR, 'themes_full.txt')
THEMES_VULNS_FILE = File.join(DATA_DIR, 'theme_vulns.json')
WP_VULNS_FILE = File.join(DATA_DIR, 'wp_vulns.json')
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml')
LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml')
# VULNS_XSD = File.join(DATA_DIR, 'vuln.xsd')
WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd')
LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd')
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
# 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
WPSCAN_VERSION = '2.5'
MIN_RUBY_VERSION = '2.1.9'
WPSCAN_VERSION = '2.9.4'
$LOAD_PATH.unshift(LIB_DIR)
$LOAD_PATH.unshift(WPSCAN_LIB_DIR)
@@ -42,22 +36,32 @@ $LOAD_PATH.unshift(MODELS_LIB_DIR)
def kali_linux?
begin
File.readlines("/etc/debian_version").grep(/^kali/i).any?
File.readlines('/etc/debian_version').grep(/^kali/i).any?
rescue
false
end
end
# Determins if installed on Windows OS
def windows?
Gem.win_platform?
end
require 'environment'
require 'zip'
def escape_glob(s)
s.gsub(/[\\\{\}\[\]\*\?]/) { |x| '\\' + x }
end
# TODO : add an exclude pattern ?
def require_files_from_directory(absolute_dir_path, files_pattern = '*.rb')
files = Dir[File.join(absolute_dir_path, files_pattern)]
files = Dir[File.join(escape_glob(absolute_dir_path), files_pattern)]
# Files in the root dir are loaded first, then those in the subdirectories
files.sort_by { |file| [file.count("/"), file] }.each do |f|
files.sort_by { |file| [file.count('/'), file] }.each do |f|
f = File.expand_path(f)
#puts "require #{f}" # Used for debug
# puts "require #{f}" # Used for debug
require f
end
end
@@ -73,13 +77,56 @@ 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)
content = File.read(LAST_UPDATE_FILE)
date = Time.parse(content) rescue nil
end
date
end
# Was it 5 days ago?
def update_required?
date = last_update
day_seconds = 24 * 60 * 60
five_days_ago = Time.now - (5 * day_seconds)
(true if date.nil?) or (date < five_days_ago)
end
# Define colors
def colorize(text, color_code)
if $COLORSWITCH
@@ -110,19 +157,21 @@ def blue(text)
end
def critical(text)
red(text)
$exit_code += 1 if defined?($exit_code) # hack for undefined var via rspec
"#{red('[!]')} #{text}"
end
def warning(text)
amber(text)
$exit_code += 1 if defined?($exit_code) # hack for undefined var via rspec
"#{amber('[!]')} #{text}"
end
def info(text)
green(text)
"#{green('[+]')} #{text}"
end
def notice(text)
blue(text)
"#{blue('[i]')} #{text}"
end
# our 1337 banner
@@ -130,15 +179,15 @@ def banner
puts '_______________________________________________________________'
puts ' __ _______ _____ '
puts ' \\ \\ / / __ \\ / ____| '
puts ' \\ \\ /\\ / /| |__) | (___ ___ __ _ _ __ '
puts ' \\ \\ /\\ / /| |__) | (___ ___ __ _ _ __ ®'
puts ' \\ \\/ \\/ / | ___/ \\___ \\ / __|/ _` | \'_ \\ '
puts ' \\ /\\ / | | ____) | (__| (_| | | | |'
puts ' \\/ \\/ |_| |_____/ \\___|\\__,_|_| |_|'
puts
puts ' WordPress Security Scanner by the WPScan Team '
puts " Version #{WPSCAN_VERSION}"
puts ' Sponsored by the RandomStorm Open Source Initiative'
puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, pvdl, @_FireFart_'
puts ' Sponsored by Sucuri - https://sucuri.net'
puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_'
puts '_______________________________________________________________'
puts
end
@@ -209,7 +258,11 @@ end
#
# @return [ Integer ] The number of lines in the given file
def count_file_lines(file)
`wc -l #{file.shellescape}`.split[0].to_i
if windows?
`findstr /R /N "^" #{file.shellescape} | find /C ":"`.split[0].to_i
else
`wc -l #{file.shellescape}`.split[0].to_i
end
end
# Truncates a string to a specific length and adds ... at the end
@@ -226,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
@@ -243,3 +308,25 @@ end
def directory_listing_enabled?(url)
Browser.get(url.to_s).body[%r{<title>Index of}] ? true : false
end
def url_encode(str)
CGI.escape(str).gsub("+", "%20")
end
# 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

@@ -3,10 +3,9 @@
# DB Updater
class DbUpdater
FILES = %w(
local_vulnerable_files.xml local_vulnerable_files.xsd malwares.txt
plugins_full.txt plugins.txt themes_full.txt themes.txt
local_vulnerable_files.xml local_vulnerable_files.xsd
timthumbs.txt user-agents.txt wp_versions.xml wp_versions.xsd
plugin_vulns.json theme_vulns.json wp_vulns.json
wordpresses.json plugins.json themes.json LICENSE
)
attr_reader :repo_directory
@@ -14,21 +13,29 @@ 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
def request_params
{
ssl_verifyhost: 2,
ssl_verifypeer: true
ssl_verifypeer: true,
accept_encoding: 'gzip, deflate',
timeout: 300,
connecttimeout: 20
}
end
# @return [ String ] The raw file URL associated with the given filename
def remote_file_url(filename)
"https://raw.githubusercontent.com/wpscanteam/vulndb/master/#{filename}"
"https://data.wpscan.org/#{filename}"
end
# @return [ String ] The checksum of the associated remote filename
@@ -36,8 +43,8 @@ class DbUpdater
url = "#{remote_file_url(filename)}.sha512"
res = Browser.get(url, request_params)
fail "Unable to get #{url}" unless res.code == 200
res.body
fail DownloadError, res if res.timed_out? || res.code != 200
res.body.chomp
end
def local_file_path(filename)
@@ -72,8 +79,8 @@ class DbUpdater
file_url = remote_file_url(filename)
res = Browser.get(file_url, request_params)
fail "Error while downloading #{file_url}" unless res.code == 200
File.write(file_path, res.body)
fail DownloadError, res if res.timed_out? || res.code != 200
File.open(file_path, 'wb') { |f| f.write(res.body) }
local_file_checksum(filename)
end
@@ -81,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
@@ -93,12 +100,13 @@ 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
unless dl_checksum == db_checksum
fail "#{filename}: checksums do not match"
raise ChecksumError.new(File.read(local_file_path(filename))), "#{filename}: checksums do not match (local: #{dl_checksum} remote: #{db_checksum})"
end
rescue => e
puts ' [i] Restoring Backup due to error' if verbose
@@ -111,5 +119,8 @@ class DbUpdater
end
end
end
# write last_update date to file
File.write(LAST_UPDATE_FILE, Time.now)
end
end

41
lib/common/errors.rb Normal file
View File

@@ -0,0 +1,41 @@
# HTTP Error
class HttpError < StandardError
attr_reader :response
# @param [ Typhoeus::Response ] response
def initialize(response)
@response = response
end
def failure_details
msg = response.effective_url
if response.code == 0 || response.timed_out?
msg += " (#{response.return_message})"
else
msg += " (status: #{response.code})"
end
msg
end
def message
"HTTP Error: #{failure_details}"
end
end
# Used in the Updater
class DownloadError < HttpError
def message
"Unable to get #{failure_details}"
end
end
class ChecksumError < StandardError
attr_reader :file
def initialize(file)
@file = file
end
end

View File

@@ -1,35 +1,5 @@
# encoding: UTF-8
# Since ruby 1.9.2, URI::escape is obsolete
# See http://rosettacode.org/wiki/URL_encoding#Ruby and http://www.ruby-forum.com/topic/207489
if RUBY_VERSION >= '1.9.2'
module URI
extend self
def escape(str)
URI::Parser.new.escape(str)
end
alias :encode :escape
end
end
if RUBY_VERSION < '1.9'
class Array
# Fix for grep with symbols in ruby <= 1.8.7
def _grep_(regexp)
matches = []
self.each do |value|
value = value.to_s
matches << value if value.match(regexp)
end
matches
end
alias_method :grep, :_grep_
end
end
# This is used in WpItem::Existable
module Typhoeus
class Response
@@ -49,60 +19,19 @@ end
# Override for puts to enable logging
def puts(o = '')
# remove color for logging
if o.respond_to?(:gsub)
temp = o.gsub(/\e\[\d+m/, '')
File.open(LOG_FILE, 'a+') { |f| f.puts(temp) }
if $log && o.respond_to?(:gsub)
temp = o.gsub(/\e\[\d+m/, '') # remove color for logging
File.open($log, 'a+') { |f| f.puts(temp) }
end
super(o)
end
module Terminal
class Table
def render
separator = Separator.new(self)
buffer = [separator]
unless @title.nil?
buffer << Row.new(self, [title_cell_options])
buffer << separator
end
unless @headings.cells.empty?
buffer << @headings
buffer << separator
end
buffer += @rows
buffer << separator
buffer.map { |r| style.margin_left + r.render }.join("\n")
end
alias :to_s :render
class Style
@@defaults = {
:border_x => "-", :border_y => "|", :border_i => "+",
:padding_left => 1, :padding_right => 1,
:margin_left => '',
:width => nil, :alignment => nil
}
attr_accessor :margin_left
attr_accessor :border_x
attr_accessor :border_y
attr_accessor :border_i
attr_accessor :padding_left
attr_accessor :padding_right
attr_accessor :width
attr_accessor :alignment
end
end
end
class Numeric
def bytes_to_human
units = %w{B KB MB GB TB}
e = (Math.log(self)/Math.log(1024)).floor
s = "%.3f" % (to_f / 1024**e)
e = (Math.log(abs)/Math.log(1024)).floor
s = '%.3f' % (abs.to_f / 1024**e)
s.sub(/\.?0*$/, ' ' + units[e])
end
end

123
lib/common/models/vulnerability.rb Executable file → Normal file
View File

@@ -1,61 +1,62 @@
# encoding: UTF-8
require 'vulnerability/output'
require 'vulnerability/urls'
class Vulnerability
include Vulnerability::Output
include Vulnerability::Urls
attr_accessor :title, :references, :type, :fixed_in
#
# @param [ String ] title The title of the vulnerability
# @param [ String ] type The type of the vulnerability
# @param [ Hash ] references References
# @param [ String ] fixed_in Vuln fixed in Version X
#
# @return [ Vulnerability ]
def initialize(title, type, references = {}, fixed_in = '')
@title = title
@type = type
@references = references
@fixed_in = fixed_in
end
# @param [ Vulnerability ] other
#
# @return [ Boolean ]
# :nocov:
def ==(other)
title == other.title &&
type == other.type &&
references == other.references &&
fixed_in == other.fixed_in
end
# :nocov:
# Create the Vulnerability from the json_item
#
# @param [ Hash ] json_item
#
# @return [ Vulnerability ]
def self.load_from_json_item(json_item)
references = {}
%w(id url cve secunia osvdb metasploit exploitdb).each do |key|
if json_item[key]
json_item[key] = [json_item[key]] if json_item[key].class != Array
references[key] = json_item[key]
end
end
new(
json_item['title'],
json_item['type'],
references,
json_item['fixed_in'],
)
end
end
# encoding: UTF-8
require 'vulnerability/output'
require 'vulnerability/urls'
class Vulnerability
include Vulnerability::Output
include Vulnerability::Urls
attr_accessor :title, :references, :type, :fixed_in
#
# @param [ String ] title The title of the vulnerability
# @param [ String ] type The type of the vulnerability
# @param [ Hash ] references References
# @param [ String ] fixed_in Vuln fixed in Version X
#
# @return [ Vulnerability ]
def initialize(title, type, references = {}, fixed_in = '')
@title = title
@type = type
@references = references
@fixed_in = fixed_in
end
# @param [ Vulnerability ] other
#
# @return [ Boolean ]
# :nocov:
def ==(other)
title == other.title &&
type == other.type &&
references == other.references &&
fixed_in == other.fixed_in
end
# :nocov:
# Create the Vulnerability from the json_item
#
# @param [ Hash ] json_item
#
# @return [ Vulnerability ]
def self.load_from_json_item(json_item)
references = {}
references['id'] = [json_item['id']]
%w(url cve secunia osvdb metasploit exploitdb).each do |key|
if json_item['references'][key]
json_item['references'][key] = [json_item['references'][key]] if json_item['references'][key].class != Array
references[key] = json_item['references'][key]
end
end
new(
json_item['title'],
json_item['type'],
references,
json_item['fixed_in']
)
end
end

View File

@@ -2,21 +2,22 @@
class Vulnerability
module Output
# output the vulnerability
def output(verbose = false)
puts
puts "#{critical('[!]')} Title: #{title}"
puts critical("Title: #{title}")
references.each do |key, urls|
methodname = "url_#{key}"
urls.each do |u|
next unless respond_to?(methodname)
url = send(methodname, u)
puts " Reference: #{url}" if url
end
end
if !fixed_in.nil?
puts "#{notice('[i]')} Fixed in: #{fixed_in}"
end
puts notice("Fixed in: #{fixed_in}") if fixed_in
end
end
end

View File

@@ -6,31 +6,39 @@ class Vulnerability
def url_metasploit(module_path)
# remove leading slash
module_path = module_path.sub(/^\//, '')
"http://www.rapid7.com/db/modules/#{module_path}"
"https://www.rapid7.com/db/modules/#{module_path}"
end
def url_url(url)
url
end
def url_cve(cve)
"http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-#{cve}"
def url_cve(id)
"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-#{id}"
end
def url_osvdb(id)
"http://osvdb.org/#{id}"
"http://osvdb.org/show/osvdb/#{id}"
end
def url_secunia(id)
"https://secunia.com/advisories/#{id}"
"https://secunia.com/advisories/#{id}/"
end
def url_exploitdb(id)
"http://www.exploit-db.com/exploits/#{id}/"
"https://www.exploit-db.com/exploits/#{id}/"
end
def url_id(id)
"https://wpvulndb.com/vulnerability/#{id}"
"https://wpvulndb.com/vulnerabilities/#{id}"
end
def url_packetstorm(id)
"http://packetstormsecurity.com/files/#{id}/"
end
def url_securityfocus(id)
"http://www.securityfocus.com/bid/#{id}/"
end
end
end

224
lib/common/models/wp_item.rb Executable file → Normal file
View File

@@ -1,103 +1,121 @@
# encoding: UTF-8
require 'wp_item/findable'
require 'wp_item/versionable'
require 'wp_item/vulnerable'
require 'wp_item/existable'
require 'wp_item/infos'
require 'wp_item/output'
class WpItem
extend WpItem::Findable
include WpItem::Versionable
include WpItem::Vulnerable
include WpItem::Existable
include WpItem::Infos
include WpItem::Output
attr_reader :path
attr_accessor :name, :wp_content_dir, :wp_plugins_dir
# @return [ Array ]
# Make it private ?
def allowed_options
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :vulns_file]
end
# @param [ URI ] target_base_uri
# @param [ Hash ] options See allowed_option
#
# @return [ WpItem ]
def initialize(target_base_uri, options = {})
options[:wp_content_dir] ||= 'wp-content'
options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins'
set_options(options)
forge_uri(target_base_uri)
end
# @param [ Hash ] options
#
# @return [ void ]
def set_options(options)
allowed_options.each do |allowed_option|
if options.has_key?(allowed_option)
method = :"#{allowed_option}="
if self.respond_to?(method)
self.send(method, options[allowed_option])
else
raise "#{self.class} does not respond to #{method}"
end
end
end
end
private :set_options
# @param [ URI ] target_base_uri
#
# @return [ void ]
def forge_uri(target_base_uri)
@uri = target_base_uri
end
# @return [ URI ] The uri to the WpItem, with the path if present
def uri
path ? @uri.merge(path) : @uri
end
# @return [ String ] The url to the WpItem
def url; uri.to_s end
# Sets the path
#
# Variable, such as $wp-plugins$ and $wp-content$ can be used
# and will be replace by their value
#
# @param [ String ] path
#
# @return [ void ]
def path=(path)
@path = URI.encode(
path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
)
end
# @param [ WpItem ] other
def <=>(other)
name <=> other.name
end
# @param [ WpItem ] other
def ==(other)
name === other.name
end
# @param [ WpItem ] other
def ===(other)
self == other && version === other.version
end
end
# encoding: UTF-8
require 'wp_item/findable'
require 'wp_item/versionable'
require 'wp_item/vulnerable'
require 'wp_item/existable'
require 'wp_item/infos'
require 'wp_item/output'
class WpItem
extend WpItem::Findable
include WpItem::Versionable
include WpItem::Vulnerable
include WpItem::Existable
include WpItem::Infos
include WpItem::Output
attr_reader :path
attr_accessor :name, :wp_content_dir, :wp_plugins_dir
# @return [ Array ]
# Make it private ?
def allowed_options
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :db_file]
end
# @param [ URI ] target_base_uri
# @param [ Hash ] options See allowed_option
#
# @return [ WpItem ]
def initialize(target_base_uri, options = {})
options[:wp_content_dir] ||= 'wp-content'
options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins'
set_options(options)
forge_uri(target_base_uri)
end
def identifier
@identifier ||= name
end
# @return [ Hash ]
def db_data
@db_data ||= json(db_file)[identifier] || {}
end
def latest_version
db_data['latest_version']
end
def last_updated
db_data['last_updated']
end
def popular?
db_data['popular']
end
# @param [ Hash ] options
#
# @return [ void ]
def set_options(options)
allowed_options.each do |allowed_option|
if options.has_key?(allowed_option)
method = :"#{allowed_option}="
if self.respond_to?(method)
self.send(method, options[allowed_option])
else
raise "#{self.class} does not respond to #{method}"
end
end
end
end
private :set_options
# @param [ URI ] target_base_uri
#
# @return [ void ]
def forge_uri(target_base_uri)
@uri = target_base_uri
end
# @return [ URI ] The uri to the WpItem, with the path if present
def uri
path ? @uri.merge(path) : @uri
end
# @return [ String ] The url to the WpItem
def url; uri.to_s end
# Sets the path
#
# Variable, such as $wp-plugins$ and $wp-content$ can be used
# and will be replace by their value
#
# @param [ String ] path
#
# @return [ void ]
def path=(path)
@path = path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
end
# @param [ WpItem ] other
def <=>(other)
name <=> other.name
end
# @param [ WpItem ] other
def ==(other)
name === other.name
end
# @param [ WpItem ] other
def ===(other)
self == other && version === other.version
end
end

100
lib/common/models/wp_item/existable.rb Executable file → Normal file
View File

@@ -1,50 +1,50 @@
# encoding: UTF-8
class WpItem
module Existable
# Check the existence of the WpItem
# If the response is supplied, it's used for the verification
# Otherwise a new request is done
#
# @param [ Hash ] options See exists_from_response?
# @param [ Typhoeus::Response ] response
#
# @return [ Boolean ]
def exists?(options = {}, response = nil)
unless response
response = Browser.get(url)
end
exists_from_response?(response, options)
end
protected
# @param [ Typhoeus::Response ] response
# @param [ options ] options
#
# @option options [ Hash ] :error_404_hash The hash of the error 404 page
# @option options [ Hash ] :homepage_hash The hash of the homepage
# @option options [ Hash ] :exclude_content A regexp with the pattern to exclude from the body of the response
#
# @return [ Boolean ]
def exists_from_response?(response, options = {})
# 301 included as some items do a self-redirect
# Redirects to the 404 and homepage should be ignored (unless dynamic content is used)
# by the page hashes (error_404_hash & homepage_hash)
if [200, 401, 403, 301].include?(response.code)
if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash])
if options[:exclude_content]
unless response.body.match(options[:exclude_content])
return true
end
else
return true
end
end
end
false
end
end
end
# encoding: UTF-8
class WpItem
module Existable
# Check the existence of the WpItem
# If the response is supplied, it's used for the verification
# Otherwise a new request is done
#
# @param [ Hash ] options See exists_from_response?
# @param [ Typhoeus::Response ] response
#
# @return [ Boolean ]
def exists?(options = {}, response = nil)
unless response
response = Browser.get(url)
end
exists_from_response?(response, options)
end
protected
# @param [ Typhoeus::Response ] response
# @param [ options ] options
#
# @option options [ Hash ] :error_404_hash The hash of the error 404 page
# @option options [ Hash ] :homepage_hash The hash of the homepage
# @option options [ Hash ] :exclude_content A regexp with the pattern to exclude from the body of the response
#
# @return [ Boolean ]
def exists_from_response?(response, options = {})
# 301 included as some items do a self-redirect
# Redirects to the 404 and homepage should be ignored (unless dynamic content is used)
# by the page hashes (error_404_hash & homepage_hash)
if [200, 401, 403, 301].include?(response.code)
if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash])
if options[:exclude_content]
unless response.body.match(options[:exclude_content])
return true
end
else
return true
end
end
end
false
end
end
end

37
lib/common/models/wp_item/findable.rb Executable file → Normal file
View File

@@ -1,19 +1,18 @@
# encoding: UTF-8
class WpItem
attr_reader :found_from
# Sets the found_from attribute
#
# @param [ String ] method The method which found the WpItem
#
# @return [ void ]
def found_from=(method)
found = method[%r{find_from_(.*)}, 1]
@found_from = found.gsub('_', ' ') if found
end
module Findable
end
end
# encoding: UTF-8
class WpItem
attr_reader :found_from
# Sets the found_from attribute
#
# @param [ String ] method The method which found the WpItem
#
# @return [ void ]
def found_from=(method)
@found_from = method.to_s.gsub(/find_from_/, '').gsub(/_/, ' ')
end
module Findable
end
end

View File

@@ -5,25 +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
%w{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
@@ -33,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

@@ -5,20 +5,25 @@ class WpItem
# @return [ Void ]
def output(verbose = false)
outdated = VersionCompare.lesser?(version, latest_version) if latest_version
puts
puts "#{info('[+]')} Name: #{self}" #this will also output the version number if detected
puts info("Name: #{self}") #this will also output the version number if detected
puts " | Latest version: #{latest_version} #{'(up to date)' if version}" if latest_version && !outdated
puts " | Last updated: #{last_updated}" if last_updated
puts " | Location: #{url}"
#puts " | WordPress: #{wordpress_url}" if wordpress_org_item?
puts " | Readme: #{readme_url}" if has_readme?
puts " | Changelog: #{changelog_url}" if has_changelog?
puts "#{warning('[!]')} Directory listing is enabled: #{url}" if has_directory_listing?
puts "#{warning('[!]')} An error_log file has been found: #{error_log_url}" if has_error_log?
puts warning("The version is out of date, the latest version is #{latest_version}") if latest_version && outdated
puts warning("Directory listing is enabled: #{url}") if has_directory_listing?
puts warning("An error_log file has been found: #{error_log_url}") if has_error_log?
additional_output(verbose) if respond_to?(:additional_output)
if version.nil? && vulnerabilities.length > 0
puts
puts "#{warning('[+]')} We could not determine a version so all vulnerabilities are printed out"
puts warning('We could not determine the version installed. All of the past known vulnerabilities will be output to allow you to do your own manual investigation.')
end
vulnerabilities.output

82
lib/common/models/wp_item/versionable.rb Executable file → Normal file
View File

@@ -1,29 +1,53 @@
# encoding: UTF-8
class WpItem
attr_writer :version
module Versionable
# Get the version from the readme.txt
#
# @return [ String ] The version number
def version
unless @version
# This check is needed because readme_url can return nil
if has_readme?
response = Browser.get(readme_url)
@version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1]
end
end
@version
end
# @return [ String ]
def to_s
item_version = self.version
"#@name#{' - v' + item_version.strip if item_version}"
end
end
end
# encoding: UTF-8
class WpItem
attr_writer :version
module Versionable
# Get the version from the readme.txt
#
# @return [ String ] The version number
def version
unless @version
# This check is needed because readme_url can return nil
if has_readme?
response = Browser.get(readme_url)
@version = extract_version(response.body)
end
end
@version
end
# @return [ String ]
def to_s
item_version = self.version
"#{@name}#{' - v' + item_version.strip if item_version}"
end
# Extracts the version number from a given string/body
#
# @return [ String ] detected version
def extract_version(body)
version = body[/\b(?:stable tag|version):\s*(?!trunk)([0-9a-z\.-]+)/i, 1]
if version.nil? || version !~ /[0-9]+/
extracted_versions = body.scan(/[=]+\s+(?:v(?:ersion)?\s*)?([0-9\.-]+)[ \ta-z0-9\(\)\.-]*[=]+/i)
return if extracted_versions.nil? || extracted_versions.length == 0
extracted_versions.flatten!
# must contain at least one number
extracted_versions = extracted_versions.select { |x| x =~ /[0-9]+/ }
sorted = extracted_versions.sort { |x,y|
begin
Gem::Version.new(x) <=> Gem::Version.new(y)
rescue
0
end
}
return sorted.last
else
return version
end
end
end
end

93
lib/common/models/wp_item/vulnerable.rb Executable file → Normal file
View File

@@ -1,49 +1,44 @@
# encoding: UTF-8
class WpItem
module Vulnerable
attr_accessor :vulns_file, :identifier
# Get the vulnerabilities associated to the WpItem
# Filters out already fixed vulnerabilities
#
# @return [ Vulnerabilities ]
def vulnerabilities
json = json(vulns_file)
vulnerabilities = Vulnerabilities.new
json.each do |item|
asset = item[identifier]
if asset
asset['vulnerabilities'].each do |vulnerability|
vulnerability = Vulnerability.load_from_json_item(vulnerability)
vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
end
end
end
vulnerabilities
end
def vulnerable?
vulnerabilities.empty? ? false : true
end
# Checks if a item is vulnerable to a specific vulnerability
#
# @param [ Vulnerability ] vuln Vulnerability to check the item against
#
# @return [ Boolean ]
def vulnerable_to?(vuln)
if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version)
return true
end
else
return true
end
return false
end
end
end
# encoding: UTF-8
class WpItem
module Vulnerable
attr_accessor :db_file, :identifier
# Get the vulnerabilities associated to the WpItem
# Filters out already fixed vulnerabilities
#
# @return [ Vulnerabilities ]
def vulnerabilities
return @vulnerabilities if @vulnerabilities
@vulnerabilities = Vulnerabilities.new
[*db_data['vulnerabilities']].each do |vulnerability|
vulnerability = Vulnerability.load_from_json_item(vulnerability)
@vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
end
@vulnerabilities
end
def vulnerable?
vulnerabilities.empty? ? false : true
end
# Checks if a item is vulnerable to a specific vulnerability
#
# @param [ Vulnerability ] vuln Vulnerability to check the item against
#
# @return [ Boolean ]
def vulnerable_to?(vuln)
if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version)
return true
end
else
return true
end
return false
end
end
end

33
lib/common/models/wp_plugin.rb Executable file → Normal file
View File

@@ -1,17 +1,16 @@
# encoding: UTF-8
require 'wp_plugin/vulnerable'
class WpPlugin < WpItem
include WpPlugin::Vulnerable
# Sets the @uri
#
# @param [ URI ] target_base_uri The URI of the wordpress blog
#
# @return [ void ]
def forge_uri(target_base_uri)
@uri = target_base_uri.merge(URI.encode(wp_plugins_dir + '/' + name + '/'))
end
end
# encoding: UTF-8
class WpPlugin < WpItem
# Sets the @uri
#
# @param [ URI ] target_base_uri The URI of the wordpress blog
#
# @return [ void ]
def forge_uri(target_base_uri)
@uri = target_base_uri.merge("#{wp_plugins_dir}/#{url_encode(name)}/")
end
def db_file
@db_file ||= PLUGINS_FILE
end
end

View File

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

73
lib/common/models/wp_theme.rb Executable file → Normal file
View File

@@ -1,36 +1,37 @@
# encoding: UTF-8
require 'wp_theme/findable'
require 'wp_theme/versionable'
require 'wp_theme/vulnerable'
require 'wp_theme/info'
require 'wp_theme/output'
require 'wp_theme/childtheme'
class WpTheme < WpItem
extend WpTheme::Findable
include WpTheme::Versionable
include WpTheme::Vulnerable
include WpTheme::Info
include WpTheme::Output
include WpTheme::Childtheme
attr_accessor :referenced_url
def allowed_options; super << :referenced_url end
# Sets the @uri
#
# @param [ URI ] target_base_uri The URI of the wordpress blog
#
# @return [ void ]
def forge_uri(target_base_uri)
@uri = target_base_uri.merge(URI.encode(wp_content_dir + '/themes/' + name + '/'))
end
# @return [ String ] The url to the theme stylesheet
def style_url
@uri.merge('style.css').to_s
end
end
# encoding: UTF-8
require 'wp_theme/findable'
require 'wp_theme/versionable'
require 'wp_theme/info'
require 'wp_theme/output'
require 'wp_theme/childtheme'
class WpTheme < WpItem
extend WpTheme::Findable
include WpTheme::Versionable
include WpTheme::Info
include WpTheme::Output
include WpTheme::Childtheme
attr_accessor :referenced_url
def allowed_options; super << :referenced_url end
# Sets the @uri
#
# @param [ URI ] target_base_uri The URI of the wordpress blog
#
# @return [ void ]
def forge_uri(target_base_uri)
@uri = target_base_uri.merge("#{wp_content_dir}/themes/#{url_encode(name)}/")
end
# @return [ String ] The url to the theme stylesheet
def style_url
@uri.merge('style.css').to_s
end
def db_file
@db_file ||= THEMES_FILE
end
end

View File

@@ -3,6 +3,10 @@
class WpTheme < WpItem
module Childtheme
def parent_theme_limit
3
end
def is_child_theme?
return true unless @theme_template.nil?
false
@@ -10,7 +14,7 @@ class WpTheme < WpItem
def get_parent_theme_style_url
if is_child_theme?
return style_url.sub("/#{name}/style.css", "/#@theme_template/style.css")
return style_url.sub("/#{name}/style.css", "/#{@theme_template}/style.css")
end
nil
end

134
lib/common/models/wp_theme/findable.rb Executable file → Normal file
View File

@@ -1,70 +1,64 @@
# encoding: UTF-8
class WpTheme < WpItem
module Findable
# Find the main theme of the blog
#
# @param [ URI ] target_uri
#
# @return [ WpTheme ]
def find(target_uri)
methods.grep(/^find_from_/).each do |method|
if wp_theme = self.send(method, target_uri)
wp_theme.found_from = method
return wp_theme
end
end
nil
end
protected
# Discover the wordpress theme by parsing the css link rel
#
# @param [ URI ] target_uri
#
# @return [ WpTheme ]
def find_from_css_link(target_uri)
response = Browser.get_and_follow_location(target_uri.to_s)
# https + domain is optional because of relative links
matches = /(?:https?:\/\/[^"']+)?\/([^\/]+)\/themes\/([^"'\/]+)[^"']*\/style.css/i.match(response.body)
if matches
return new(
target_uri,
{
name: matches[2],
referenced_url: matches[0],
wp_content_dir: matches[1]
}
)
end
end
# @param [ URI ] target_uri
#
# @return [ WpTheme ]
def find_from_wooframework(target_uri)
body = Browser.get(target_uri.to_s).body
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
if matches = regexp.match(body)
woo_theme_name = matches[1]
woo_theme_version = matches[2]
#woo_framework_version = matches[3] # Not used at this time
return new(
target_uri,
{
name: woo_theme_name,
version: woo_theme_version
}
)
end
end
end
end
# encoding: UTF-8
class WpTheme < WpItem
module Findable
# Find the main theme of the blog
#
# @param [ URI ] target_uri
#
# @return [ WpTheme ]
def find(target_uri)
methods.grep(/^find_from_/).each do |method|
if wp_theme = self.send(method, target_uri)
wp_theme.found_from = method.to_s
return wp_theme
end
end
nil
end
protected
# Discover the wordpress theme by parsing the css link rel
#
# @param [ URI ] target_uri
#
# @return [ WpTheme ]
def find_from_css_link(target_uri)
response = Browser.get_and_follow_location(target_uri.to_s)
# https + domain is optional because of relative links
return unless response.body =~ %r{(?:https?://[^"']+/)?([^"'/\s]+)/themes/([^"'/]+)[^"']*/style\.css}i
new(
target_uri,
name: Regexp.last_match[2],
referenced_url: Regexp.last_match[0],
wp_content_dir: Regexp.last_match[1]
)
end
# @param [ URI ] target_uri
#
# @return [ WpTheme ]
def find_from_wooframework(target_uri)
body = Browser.get(target_uri.to_s).body
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
if matches = regexp.match(body)
woo_theme_name = matches[1]
woo_theme_version = matches[2]
#woo_framework_version = matches[3] # Not used at this time
return new(
target_uri,
name: woo_theme_name,
version: woo_theme_version
)
end
end
end
end

View File

@@ -6,20 +6,20 @@ class WpTheme
# @return [ Void ]
def additional_output(verbose = false)
parse_style
theme_desc = verbose ? @theme_description : truncate(@theme_description, 100)
puts " | Style URL: #{style_url}"
puts " | Referenced style.css: #{referenced_url}" if referenced_url && referenced_url != style_url
puts " | Theme Name: #@theme_name" if @theme_name
puts " | Theme URI: #@theme_uri" if @theme_uri
puts " | Description: #{theme_desc}"
puts " | Author: #@theme_author" if @theme_author
puts " | Author URI: #@theme_author_uri" if @theme_author_uri
puts " | Template: #@theme_template" if @theme_template and verbose
puts " | License: #@theme_license" if @theme_license and verbose
puts " | License URI: #@theme_license_uri" if @theme_license_uri and verbose
puts " | Tags: #@theme_tags" if @theme_tags and verbose
puts " | Text Domain: #@theme_text_domain" if @theme_text_domain and verbose
puts " | Theme Name: #{@theme_name}" if @theme_name
puts " | Theme URI: #{@theme_uri}" if @theme_uri
puts " | Description: #{theme_desc}" if theme_desc
puts " | Author: #{@theme_author}" if @theme_author
puts " | Author URI: #{@theme_author_uri}" if @theme_author_uri
puts " | Template: #{@theme_template}" if @theme_template and verbose
puts " | License: #{@theme_license}" if @theme_license and verbose
puts " | License URI: #{@theme_license_uri}" if @theme_license_uri and verbose
puts " | Tags: #{@theme_tags}" if @theme_tags and verbose
puts " | Text Domain: #{@theme_text_domain}" if @theme_text_domain and verbose
end
end

26
lib/common/models/wp_theme/versionable.rb Executable file → Normal file
View File

@@ -1,17 +1,9 @@
# encoding: UTF-8
class WpTheme < WpItem
module Versionable
def version
unless @version
@version = Browser.get(style_url).body[%r{Version:\s*([^\s]+)}i, 1]
# Get Version from readme.txt
@version ||= super
end
@version
end
end
end
# encoding: UTF-8
class WpTheme < WpItem
module Versionable
def version
@version ||= Browser.get(style_url).body[%r{Version:\s*(?!trunk)([0-9a-z\.-]+)}i, 1]
end
end
end

View File

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

40
lib/common/models/wp_timthumb.rb Executable file → Normal file
View File

@@ -1,20 +1,20 @@
# encoding: UTF-8
require 'wp_timthumb/versionable'
require 'wp_timthumb/existable'
require 'wp_timthumb/output'
require 'wp_timthumb/vulnerable'
class WpTimthumb < WpItem
include WpTimthumb::Versionable
include WpTimthumb::Existable
include WpTimthumb::Output
include WpTimthumb::Vulnerable
# @param [ WpTimthumb ] other
#
# @return [ Boolean ]
def ==(other)
url == other.url
end
end
# encoding: UTF-8
require 'wp_timthumb/versionable'
require 'wp_timthumb/existable'
require 'wp_timthumb/output'
require 'wp_timthumb/vulnerable'
class WpTimthumb < WpItem
include WpTimthumb::Versionable
include WpTimthumb::Existable
include WpTimthumb::Output
include WpTimthumb::Vulnerable
# @param [ WpTimthumb ] other
#
# @return [ Boolean ]
def ==(other)
url == other.url
end
end

View File

@@ -5,7 +5,7 @@ class WpTimthumb < WpItem
def output(verbose = false)
puts
puts "#{info('[+]')} #{self}" #this will also output the version number if detected
puts info("#{self}") #this will also output the version number if detected
vulnerabilities.output
end

48
lib/common/models/wp_timthumb/versionable.rb Executable file → Normal file
View File

@@ -1,24 +1,24 @@
# encoding: UTF-8
class WpTimthumb < WpItem
module Versionable
# Get the version from the body of an invalid request
# See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426
#
# @return [ String ] The version
def version
unless @version
response = Browser.get(url)
@version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1]
end
@version
end
# @return [ String ]
def to_s
"#{url}#{ ' v' + version if version}"
end
end
end
# encoding: UTF-8
class WpTimthumb < WpItem
module Versionable
# Get the version from the body of an invalid request
# See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426
#
# @return [ String ] The version
def version
unless @version
response = Browser.get(url)
@version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1]
end
@version
end
# @return [ String ]
def to_s
"#{url}#{ ' v' + version if version}"
end
end
end

View File

@@ -15,7 +15,7 @@ class WpTimthumb < WpItem
end
def check_rce_132
return rce_132_vuln unless VersionCompare.lesser_or_equal?('1.33', version)
rce_132_vuln unless VersionCompare.lesser_or_equal?('1.33', version)
end
# Vulnerable versions : > 1.35 (or >= 2.0) and < 2.8.14
@@ -24,7 +24,7 @@ class WpTimthumb < WpItem
response = Browser.get(uri.merge('?webshot=1&src=http://' + default_allowed_domains.sample))
return rce_webshot_vuln unless response.body =~ /WEBSHOT_ENABLED == true/
rce_webshot_vuln unless response.body =~ /WEBSHOT_ENABLED == true/
end
# @return [ Array<String> ] The default allowed domains (between the 2.0 and 2.8.13)

162
lib/common/models/wp_user.rb Executable file → Normal file
View File

@@ -1,81 +1,81 @@
# encoding: UTF-8
require 'wp_user/existable'
require 'wp_user/brute_forcable'
class WpUser < WpItem
include WpUser::Existable
include WpUser::BruteForcable
attr_accessor :id, :login, :display_name, :password
# @return [ Array<Symbol> ]
def allowed_options; [:id, :login, :display_name, :password] end
# @return [ URI ] The uri to the author page
def uri
if id
return @uri.merge("?author=#{id}")
else
raise 'The id is nil'
end
end
# @return [ String ]
def login_url
unless @login_url
@login_url = @uri.merge('wp-login.php').to_s
# Let's check if the login url is redirected (to https url for example)
if redirection = redirection(@login_url)
@login_url = redirection
end
end
@login_url
end
def redirection(url)
redirection = nil
response = Browser.get(url)
if response.code == 301 || response.code == 302
redirection = response.headers_hash['location']
# Let's check if there is a redirection in the redirection
if other_redirection = redirection(redirection)
redirection = other_redirection
end
end
redirection
end
# @return [ String ]
def to_s
s = "#{id}"
s << " | #{login}" if login
s << " | #{display_name}" if display_name
s
end
# @param [ WpUser ] other
def <=>(other)
id <=> other.id
end
# @param [ WpUser ] other
#
# @return [ Boolean ]
def ==(other)
self === other
end
# @param [ WpUser ] other
#
# @return [ Boolean ]
def ===(other)
id === other.id && login === other.login
end
end
# encoding: UTF-8
require 'wp_user/existable'
require 'wp_user/brute_forcable'
class WpUser < WpItem
include WpUser::Existable
include WpUser::BruteForcable
attr_accessor :id, :login, :display_name, :password
# @return [ Array<Symbol> ]
def allowed_options; [:id, :login, :display_name, :password] end
# @return [ URI ] The uri to the author page
def uri
if id
@uri.merge("?author=#{id}")
else
raise 'The id is nil'
end
end
# @return [ String ]
def login_url
unless @login_url
@login_url = @uri.merge('wp-login.php').to_s
# Let's check if the login url is redirected (to https url for example)
if redirection = redirection(@login_url)
@login_url = redirection
end
end
@login_url
end
def redirection(url)
redirection = nil
response = Browser.get(url)
if response.code == 301 || response.code == 302
redirection = response.headers_hash['location']
# Let's check if there is a redirection in the redirection
if other_redirection = redirection(redirection)
redirection = other_redirection
end
end
redirection
end
# @return [ String ]
def to_s
s = "#{id}"
s << " | #{login}" if login
s << " | #{display_name}" if display_name
s
end
# @param [ WpUser ] other
def <=>(other)
id <=> other.id
end
# @param [ WpUser ] other
#
# @return [ Boolean ]
def ==(other)
self === other
end
# @param [ WpUser ] other
#
# @return [ Boolean ]
def ===(other)
id === other.id && login === other.login
end
end

View File

@@ -3,6 +3,8 @@
class WpUser < WpItem
module BruteForcable
attr_reader :progress_bar
# Brute force the user with the wordlist supplied
#
# It can take a long time to queue 2 million requests,
@@ -25,24 +27,40 @@ class WpUser < WpItem
hydra = browser.hydra
queue_count = 0
found = false
progress_bar = self.progress_bar(count_file_lines(wordlist), options)
File.open(wordlist).each do |password|
password.chop!
if wordlist == '-'
words = ARGF
passwords_size = 10
options[:starting_at] = 0
else
words = File.open(wordlist)
passwords_size = count_file_lines(wordlist)+1
end
create_progress_bar(passwords_size, options)
words.each do |password|
password.chomp!
# A successfull login will redirect us to the redirect_to parameter
# Generate a random one on each request
unless redirect_url
random = (0...8).map { 65.+(rand(26)).chr }.join
redirect_url = "#@uri#{random}/"
redirect_url = "#{@uri}#{random}/"
end
request = login_request(password, redirect_url)
request.on_complete do |response|
progress_bar.progress += 1 if options[:show_progression] && !found
if options[:show_progression] && !found
progress_bar.progress += 1
percentage = progress_bar.progress.fdiv(progress_bar.total)
if options[:starting_at] && percentage >= 0.8
progress_bar.total *= 2
end
end
puts "\n Trying Username : #{login} Password : #{password}" if options[:verbose]
progress_bar.log(" Trying Username: #{login} Password: #{password}") if options[:verbose]
if valid_password?(response, password, redirect_url, options)
found = true
@@ -57,25 +75,27 @@ class WpUser < WpItem
if queue_count >= browser.max_threads
hydra.run
queue_count = 0
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
progress_bar.log(" Sent #{browser.max_threads} request/s ...") if options[:verbose]
end
end
# run all of the remaining requests
hydra.run
puts if options[:show_progression] # mandatory to avoid the output of the progressbar to be overriden
end
# @param [ Integer ] targets_size
# @param [ Integer ] passwords_size
# @param [ Hash ] options
#
# @return [ ProgressBar ]
# :nocov:
def progress_bar(passwords_size, options)
def create_progress_bar(passwords_size, options)
if options[:show_progression]
ProgressBar.create(
@progress_bar = ProgressBar.create(
format: '%t %a <%B> (%c / %C) %P%% %e',
title: " Brute Forcing '#{login}'",
total: passwords_size
total: passwords_size,
starting_at: options[:starting_at]
)
end
end
@@ -106,20 +126,20 @@ class WpUser < WpItem
progression = "#{info('[SUCCESS]')} Login : #{login} Password : #{password}\n\n"
valid = true
elsif response.body =~ /login_error/i
verbose = "\n Incorrect login and/or password."
verbose = "Incorrect login and/or password."
elsif response.timed_out?
progression = "#{critical('ERROR:')} Request timed out."
progression = critical('ERROR: Request timed out.')
elsif response.code == 0
progression = "#{critical('ERROR:')} No response from remote server. WAF/IPS?"
progression = critical("ERROR: No response from remote server. WAF/IPS? (#{response.return_message})")
elsif response.code.to_s =~ /^50/
progression = "#{critical('ERROR:')} Server error, try reducing the number of threads."
progression = critical('ERROR: Server error, try reducing the number of threads or use the --throttle option.')
else
progression = "#{critical('ERROR:')} We received an unknown response for #{password}..."
verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
progression = critical("ERROR: We received an unknown response for login: #{login} and password: #{password}")
verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
end
puts "\n " + progression if progression && options[:show_progression]
puts verbose if verbose && options[:verbose]
progress_bar.log(" #{progression}") if progression && options[:show_progression]
progress_bar.log(" #{verbose}") if verbose && options[:verbose]
valid || false
end

169
lib/common/models/wp_user/existable.rb Executable file → Normal file
View File

@@ -1,83 +1,86 @@
# encoding: UTF-8
class WpUser < WpItem
module Existable
# @param [ Typhoeus::Response ] response
# @param [ Hash ] options
#
# @return [ Boolean ]
def exists_from_response?(response, options = {})
load_from_response(response)
@login ? true : false
end
# Load the login and display_name from the response
#
# @param [ Typhoeus::Response ] response
#
# @return [ void ]
def load_from_response(response)
if response.code == 301 # login in location?
location = response.headers_hash['Location']
return if location.nil? || location.empty?
@login = Existable.login_from_author_pattern(location)
@display_name = Existable.display_name_from_body(
Browser.get(location).body
)
elsif response.code == 200 # login in body?
@login = Existable.login_from_body(response.body)
@display_name = Existable.display_name_from_body(response.body)
end
end
private :load_from_response
# @param [ String ] text
#
# @return [ String ] The login
def self.login_from_author_pattern(text)
text[%r{/author/([^/\b]+)/?}i, 1]
end
# @param [ String ] body
#
# @return [ String ] The login
def self.login_from_body(body)
# Feed URL with Permalinks
login = WpUser::Existable.login_from_author_pattern(body)
unless login
# No Permalinks
login = body[%r{<body class="archive author author-([^\s]+) author-(\d+)}i, 1]
end
login
end
# @note Some bodies are encoded in ASCII-8BIT, and Nokogiri doesn't support it
# So it's forced to UTF-8 when this encoding is detected
#
# @param [ String ] body
#
# @return [ String ] The display_name
def self.display_name_from_body(body)
if title_tag = body[%r{<title>([^<]+)</title>}i, 1]
title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT
title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s
# &amp; are not decoded with Nokogiri
title_tag.gsub!('&amp;', '&')
# replace UTF chars like &#187; with dummy character
title_tag.gsub!(/&#(\d+);/, '|')
name = title_tag[%r{([^|«»]+) }, 1]
return name.strip if name
end
end
end
end
# encoding: UTF-8
class WpUser < WpItem
module Existable
# @param [ Typhoeus::Response ] response
# @param [ Hash ] options
#
# @return [ Boolean ]
def exists_from_response?(response, options = {})
load_from_response(response)
@login ? true : false
end
# Load the login and display_name from the response
#
# @param [ Typhoeus::Response ] response
#
# @return [ void ]
def load_from_response(response)
if response.code == 301 # login in location?
location = response.headers_hash['Location']
return if location.nil? || location.empty?
@login = Existable.login_from_author_pattern(location)
@display_name = Existable.display_name_from_body(
Browser.get(location).body
)
elsif response.code == 200 # login in body?
@login = Existable.login_from_body(response.body)
@display_name = Existable.display_name_from_body(response.body)
end
end
private :load_from_response
# @param [ String ] text
#
# @return [ String ] The login
def self.login_from_author_pattern(text)
return unless text =~ %r{/author/([^/\b"']+)/?}i
Regexp.last_match[1].force_encoding('UTF-8')
end
# @param [ String ] body
#
# @return [ String ] The login
def self.login_from_body(body)
# Feed URL with Permalinks
login = WpUser::Existable.login_from_author_pattern(body)
unless login
# No Permalinks
login = body[%r{<body class="archive author author-([^\s]+)[ "]}i, 1]
login ? login.force_encoding('UTF-8') : nil
end
login
end
# @note Some bodies are encoded in ASCII-8BIT, and Nokogiri doesn't support it
# So it's forced to UTF-8 when this encoding is detected
#
# @param [ String ] body
#
# @return [ String ] The display_name
def self.display_name_from_body(body)
if title_tag = body[%r{<title>([^<]+)</title>}i, 1]
title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT
title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s
# &amp; are not decoded with Nokogiri
title_tag.gsub!('&amp;', '&')
# replace UTF chars like &#187; with dummy character
title_tag.gsub!(/&#(\d+);/, '|')
name = title_tag[%r{([^|«»]+) }, 1]
return name.strip if name
end
end
end
end

77
lib/common/models/wp_version.rb Executable file → Normal file
View File

@@ -1,26 +1,51 @@
# encoding: UTF-8
require 'wp_version/findable'
require 'wp_version/vulnerable'
require 'wp_version/output'
class WpVersion < WpItem
extend WpVersion::Findable
include WpVersion::Vulnerable
include WpVersion::Output
# The version number
attr_accessor :number
# @return [ Array ]
def allowed_options; super << :number << :found_from end
# @param [ WpVersion ] other
#
# @return [ Boolean ]
def ==(other)
number == other.number
end
end
# encoding: UTF-8
require 'wp_version/findable'
require 'wp_version/output'
class WpVersion < WpItem
extend WpVersion::Findable
include WpVersion::Output
# The version number
attr_accessor :number, :metadata
alias_method :version, :number # Needed to have the right behaviour in Vulnerable#vulnerable_to?
# @return [ Array ]
def allowed_options; super << :number << :found_from end
def identifier
@identifier ||= number
end
def db_file
@db_file ||= WORDPRESSES_FILE
end
# @param [ WpVersion ] other
#
# @return [ Boolean ]
def ==(other)
number == other.number
end
# @return [ Array<String> ] All the stable versions from version_file
def self.all(versions_file = WP_VERSIONS_FILE)
Nokogiri.XML(File.open(versions_file)).css('version').reduce([]) do |a, node|
a << node.text.to_s
end
end
# @return [ Hash ] Metadata for specific WP version from WORDPRESSES_FILE
def metadata(version)
json = json(db_file)
metadata = {}
temp = json[version]
if !temp.nil?
metadata[:release_date] = temp['release_date']
metadata[:changelog_url] = temp['changelog_url']
end
metadata
end
end

458
lib/common/models/wp_version/findable.rb Executable file → Normal file
View File

@@ -1,218 +1,240 @@
# encoding: UTF-8
class WpVersion < WpItem
module Findable
# Find the version of the blog designated from target_uri
#
# @param [ URI ] target_uri
# @param [ String ] wp_content_dir
# @param [ String ] wp_plugins_dir
#
# @return [ WpVersion ]
def find(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
methods.grep(/^find_from_/).each do |method|
if method === :find_from_advanced_fingerprinting
version = send(method, target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
else
version = send(method, target_uri)
end
if version
return new(target_uri, number: version, found_from: method)
end
end
nil
end
# Used to check if the version is correct: must contain at least one dot.
#
# @return [ String ]
def version_pattern
'([^\r\n"\']+\.[^\r\n"\']+)'
end
protected
# Returns the first match of <pattern> in the body of the url
#
# @param [ URI ] target_uri
# @param [ Regex ] pattern
# @param [ String ] path
#
# @return [ String ]
def scan_url(target_uri, pattern, path = nil)
url = path ? target_uri.merge(path).to_s : target_uri.to_s
response = Browser.get_and_follow_location(url)
response.body[pattern, 1]
end
#
# DO NOT Change the order of the following methods
# unless you know what you are doing
# See WpVersion.find
#
# Attempts to find the wordpress version from,
# the generator meta tag in the html source.
#
# The meta tag can be removed however it seems,
# that it is reinstated on upgrade.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_meta_generator(target_uri)
scan_url(
target_uri,
%r{name="generator" content="wordpress #{version_pattern}"}i
)
end
# Attempts to find the WordPress version from,
# the generator tag in the RSS feed source.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_rss_generator(target_uri)
scan_url(
target_uri,
%r{<generator>http://wordpress.org/\?v=#{version_pattern}</generator>}i,
'feed/'
)
end
# Attempts to find WordPress version from,
# the generator tag in the RDF feed source.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_rdf_generator(target_uri)
scan_url(
target_uri,
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{version_pattern}" />}i,
'feed/rdf/'
)
end
# Attempts to find the WordPress version from,
# the generator tag in the RSS2 feed source.
#
# Have not been able to find an example of this - Ryan
#def find_from_rss2_generator(target_uri)
# scan_url(
# target_uri,
# %r{<generator>http://wordpress.org/?v=(#{WpVersion.version_pattern})</generator>}i,
# 'feed/rss/'
# )
#end
# Attempts to find the WordPress version from,
# the generator tag in the Atom source.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_atom_generator(target_uri)
scan_url(
target_uri,
%r{<generator uri="http://wordpress.org/" version="#{version_pattern}">WordPress</generator>}i,
'feed/atom/'
)
end
# Attempts to find the WordPress version from,
# the generator tag in the comment rss source.
#
# Have not been able to find an example of this - Ryan
#def find_from_comments_rss_generator(target_uri)
# scan_url(
# target_uri,
# %r{<!-- generator="WordPress/#{WpVersion.version_pattern}" -->}i,
# 'comments/feed/'
# )
#end
# Uses data/wp_versions.xml to try to identify a
# wordpress version.
#
# It does this by using client side file hashing
#
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
#
# @param [ URI ] target_uri
# @param [ String ] wp_content_dir
# @param [ String ] wp_plugins_dir
# @param [ String ] versions_xml The path to the xml containing all versions
#
# @return [ String ] The version number
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
xml = xml(versions_xml)
# This wp_item will take care of encoding the path
# and replace variables like $wp-content$ & $wp-plugins$
wp_item = WpItem.new(target_uri,
wp_content_dir: wp_content_dir,
wp_plugins_dir: wp_plugins_dir)
xml.xpath('//file').each do |node|
wp_item.path = node.attribute('src').text
response = Browser.get(wp_item.url)
md5sum = Digest::MD5.hexdigest(response.body)
node.search('hash').each do |hash|
if hash.attribute('md5').text == md5sum
return hash.search('version').text
end
end
end
nil
end
# Attempts to find the WordPress version from the readme.html file.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_readme(target_uri)
scan_url(
target_uri,
%r{<br />\sversion #{version_pattern}}i,
'readme.html'
)
end
# Attempts to find the WordPress version from the sitemap.xml file.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_sitemap_generator(target_uri)
scan_url(
target_uri,
%r{generator="wordpress/#{version_pattern}"}i,
'sitemap.xml'
)
end
# Attempts to find the WordPress version from the p-links-opml.php file.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_links_opml(target_uri)
scan_url(
target_uri,
%r{generator="wordpress/#{version_pattern}"}i,
'wp-links-opml.php'
)
end
end
end
# encoding: UTF-8
class WpVersion < WpItem
module Findable
# Find the version of the blog designated from target_uri
#
# @param [ URI ] target_uri
# @param [ String ] wp_content_dir
# @param [ String ] wp_plugins_dir
#
# @return [ WpVersion ]
def find(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
versions = {}
methods.grep(/^find_from_/).each do |method|
if method === :find_from_advanced_fingerprinting
version = send(method, target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
else
version = send(method, target_uri)
end
if version
if versions.key?(version)
versions[version] << method.to_s
else
versions[version] = [ method.to_s ]
end
end
end
if versions.length > 0
determined_version = versions.max_by { |k, v| v.length }
if determined_version
return new(target_uri, number: determined_version[0], found_from: determined_version[1].join(', '))
end
end
nil
end
# Used to check if the version is correct: must contain at least one dot.
#
# @return [ String ]
def version_pattern
'([^\r\n"\',]+\.[^\r\n"\',]+)'
end
protected
# Returns the first match of <pattern> in the body of the url
#
# @param [ URI ] target_uri
# @param [ Regex ] pattern
# @param [ String ] path
#
# @return [ String ]
def scan_url(target_uri, pattern, path = nil)
url = path ? target_uri.merge(path).to_s : target_uri.to_s
response = Browser.get_and_follow_location(url)
response.body[pattern, 1]
end
#
# DO NOT Change the order of the following methods
# unless you know what you are doing
# See WpVersion.find
#
# Attempts to find the wordpress version from,
# the generator meta tag in the html source.
#
# The meta tag can be removed however it seems,
# that it is reinstated on upgrade.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_meta_generator(target_uri)
scan_url(
target_uri,
%r{name="generator" content="wordpress #{version_pattern}.*"}i
)
end
# Attempts to find the WordPress version from,
# the generator tag in the RSS feed source.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_rss_generator(target_uri)
scan_url(
target_uri,
%r{<generator>http://wordpress.org/\?v=#{version_pattern}</generator>}i,
'feed/'
)
end
# Attempts to find WordPress version from,
# the generator tag in the RDF feed source.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_rdf_generator(target_uri)
scan_url(
target_uri,
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{version_pattern}" />}i,
'feed/rdf/'
)
end
# Attempts to find the WordPress version from,
# the generator tag in the Atom source.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_atom_generator(target_uri)
scan_url(
target_uri,
%r{<generator uri="http://wordpress.org/" version="#{version_pattern}">WordPress</generator>}i,
'feed/atom/'
)
end
# Uses data/wp_versions.xml to try to identify a
# wordpress version.
#
# It does this by using client side file hashing
#
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
#
# @param [ URI ] target_uri
# @param [ String ] wp_content_dir
# @param [ String ] wp_plugins_dir
# @param [ String ] versions_xml The path to the xml containing all versions
#
# @return [ String ] The version number
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
xml = xml(versions_xml)
wp_item = WpItem.new(target_uri,
wp_content_dir: wp_content_dir,
wp_plugins_dir: wp_plugins_dir)
xml.xpath('//file').each do |node|
wp_item.path = node.attribute('src').text
response = Browser.get(wp_item.url)
md5sum = Digest::MD5.hexdigest(response.body)
node.search('hash').each do |hash|
if hash.attribute('md5').text == md5sum
return hash.search('version').text
end
end
end
nil
end
# Attempts to find the WordPress version from the readme.html file.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_readme(target_uri)
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.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_sitemap_generator(target_uri)
scan_url(
target_uri,
%r{generator="wordpress/#{version_pattern}"}i,
'sitemap.xml'
)
end
# Attempts to find the WordPress version from the p-links-opml.php file.
#
# @param [ URI ] target_uri
#
# @return [ String ] The version number
def find_from_links_opml(target_uri)
scan_url(
target_uri,
%r{generator="wordpress/#{version_pattern}"}i,
'wp-links-opml.php'
)
end
def find_from_stylesheets_numbers(target_uri)
wp_versions = WpVersion.all
found = {}
pattern = /\bver=([0-9\.]+)/i
Nokogiri::HTML(Browser.get(target_uri.to_s).body).css('link,script').each do |tag|
%w(href src).each do |attribute|
attr_value = tag.attribute(attribute).to_s
next if attr_value.nil? || attr_value.empty?
begin
uri = Addressable::URI.parse(attr_value)
rescue Addressable::URI::InvalidURIError
next
end
next unless uri.query && uri.query.match(pattern)
version = Regexp.last_match[1].to_s
found[version] ||= 0
found[version] += 1
end
end
found.delete_if { |v, _| !wp_versions.include?(v) }
best_guess = found.sort_by(&:last).last
# best_guess[0]: version number, [1] numbers of occurences
best_guess && best_guess[1] > 1 ? best_guess[0] : nil
end
end
end

View File

@@ -4,14 +4,25 @@ class WpVersion < WpItem
module Output
def output(verbose = false)
metadata = self.metadata(self.number)
puts
puts "#{info('[+]')} WordPress version #{self.number} identified from #{self.found_from}"
if verbose
puts info("WordPress version #{self.number} identified from #{self.found_from}")
puts " | Released: #{metadata[:release_date]}"
puts " | Changelog: #{metadata[:changelog_url]}"
else
puts info("WordPress version #{self.number} #{"(Released on #{metadata[:release_date]}) identified from #{self.found_from}" if metadata[:release_date]}")
end
vulnerabilities = self.vulnerabilities
unless vulnerabilities.empty?
puts "#{critical('[!]')} #{vulnerabilities.size} vulnerabilities identified from the version number"
if vulnerabilities.size == 1
puts critical("#{vulnerabilities.size} vulnerability identified from the version number")
else
puts critical("#{vulnerabilities.size} vulnerabilities identified from the version number")
end
vulnerabilities.output
end
end

View File

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

View File

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

View File

@@ -3,8 +3,9 @@
require 'rubygems'
version = RUBY_VERSION.dup
if Gem::Version.create(version) < Gem::Version.create(1.9)
puts "Ruby >= 1.9 required to run wpscan (You have #{version})"
if Gem::Version.create(version) < Gem::Version.create(MIN_RUBY_VERSION)
puts "Ruby >= #{MIN_RUBY_VERSION} required to run wpscan (You have #{version})"
exit(1)
end
@@ -13,28 +14,29 @@ Encoding.default_external = Encoding::UTF_8
begin
# Standard libs
require 'readline'
require 'bundler/setup' unless kali_linux?
require 'getoptlong'
require 'optparse' # Will replace getoptlong
require 'uri'
require 'time'
require 'resolv'
require 'xmlrpc/client'
require 'digest/md5'
require 'digest/sha1'
require 'readline'
require 'base64'
require 'rbconfig'
require 'pp'
require 'shellwords'
require 'fileutils'
require 'pathname'
require 'cgi'
# Third party libs
require 'typhoeus'
require 'json'
require 'yajl/json_gem'
require 'nokogiri'
require 'terminal-table'
require 'ruby-progressbar'
require 'addressable/uri'
# Custom libs
require 'common/browser'
require 'common/custom_option_parser'

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
@@ -21,6 +29,29 @@ class WebSite
@uri.to_s
end
# Checks if the remote website has ssl errors
def ssl_error?
return false unless @uri.scheme == 'https'
c = get_root_path_return_code
# http://www.rubydoc.info/github/typhoeus/ethon/Ethon/Easy:return_code
return (
c == :ssl_connect_error ||
c == :peer_failed_verification ||
c == :ssl_certproblem ||
c == :ssl_cipher ||
c == :ssl_cacert ||
c == :ssl_cacert_badfile ||
c == :ssl_issuer_error ||
c == :ssl_crl_badfile ||
c == :ssl_engine_setfailed ||
c == :ssl_engine_notfound
)
end
def get_root_path_return_code
Browser.get(@uri.to_s).return_code
end
# Checks if the remote website is up.
def online?
Browser.get(@uri.to_s).code != 0
@@ -54,10 +85,9 @@ class WebSite
redirected_uri = URI.parse(add_trailing_slash(add_http_protocol(url)))
if response.code == 301 || response.code == 302
redirection = response.headers_hash['location']
if redirection[0] == '/'
redirection = "#{redirected_uri.scheme}://#{redirected_uri.host}#{redirection}"
end
redirection = redirected_uri.merge(response.headers_hash['location']).to_s
return redirection if url == redirection # prevents infinite loop
# Let's check if there is a redirection in the redirection
if other_redirection = redirection(redirection)
@@ -69,15 +99,18 @@ class WebSite
end
# Compute the MD5 of the page
# Comments are deleted from the page to avoid cache generation details
# Comments and scripts are deleted from the page to avoid cache generation details
#
# @param [ String, Typhoeus::Response ] page The url of the response of the page
#
# @return [ String ] The MD5 hash of the page
def self.page_hash(page)
page = Browser.get(page, { followlocation: true, cache_ttl: 0 }) unless page.is_a?(Typhoeus::Response)
Digest::MD5.hexdigest(page.body.gsub(/<!--.*?-->/m, ''))
# remove comments
page = page.body.gsub(/<!--.*?-->/m, '')
# remove javascript stuff
page = page.gsub(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/m, '')
Digest::MD5.hexdigest(page)
end
def homepage_hash
@@ -96,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

@@ -15,52 +15,56 @@ class WebSite
@uri.clone.merge('robots.txt').to_s
end
# 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!
# 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
rescue URI::Error
temp = d
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,36 +1,44 @@
# encoding: UTF-8
require 'web_site'
require 'wp_target/malwares'
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::Malwares
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
def initialize(target_url, options = {})
raise Exception.new('target_url can not be nil or empty') if target_url.nil? || target_url == ''
super(target_url)
@verbose = options[:verbose]
@wp_content_dir = options[:wp_content_dir]
@wp_plugins_dir = options[:wp_plugins_dir]
@multisite = nil
@vhost = options[:vhost]
Browser.instance.referer = url
if @vhost
Browser.instance.vhost = @vhost
end
end
# check if the target website is
@@ -42,10 +50,12 @@ class WpTarget < WebSite
# Note: in the future major WPScan version, change the user-agent to see
# if the response is a 200 ?
fail "The target is responding with a 403, this might be due to a WAF or a plugin\n" \
'You should try to supply a valid user-agent via the --user-agent option' if response.code == 403
fail "The target is responding with a 403, this might be due to a WAF or a plugin.\n" \
'You should try to supply a valid user-agent via the --user-agent option or use the --random-agent option' if response.code == 403
if response.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i
dir = wp_content_dir ? wp_content_dir : 'wp-content'
if response.body =~ /["'][^"']*\/#{Regexp.escape(dir)}\/[^"']*["']/i
wordpress = true
else
@@ -72,9 +82,7 @@ class WpTarget < WebSite
# Let's check if the login url is redirected (to https url for example)
redirection = redirection(url)
if redirection
url = redirection
end
url = redirection if redirection
url
end
@@ -131,6 +139,11 @@ class WpTarget < WebSite
@uri.merge("#{wp_content_dir}/uploads/").to_s
end
# @return [ String ]
def includes_dir_url
@uri.merge("wp-includes/").to_s
end
# Script for replacing strings in wordpress databases
# reveals database credentials after hitting submit
# http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
@@ -146,7 +159,26 @@ class WpTarget < WebSite
resp.code == 200 && resp.body[%r{by interconnect}i]
end
# Script used to recover locked out admin users
# http://yoast.com/emergency-wordpress-access/
# https://codex.wordpress.org/User:MichaelH/Orphaned_Plugins_needing_Adoption/Emergency
#
# @return [ String ]
def emergency_url
@uri.merge('emergency.php').to_s
end
# @return [ Boolean ]
def emergency_exists?
resp = Browser.get(emergency_url)
resp.code == 200 && resp.body[%r{password}i]
end
def upload_directory_listing_enabled?
directory_listing_enabled?(upload_dir_url)
end
def include_directory_listing_enabled?
directory_listing_enabled?(includes_dir_url)
end
end

View File

@@ -1,50 +0,0 @@
# encoding: UTF-8
class WpTarget < WebSite
module Malwares
# Used as cache :
# nil => malwares not checked,
# [] => no malwares,
# otherwise array of malwares url found
@malwares = nil
def has_malwares?(malwares_file_path = nil)
!malwares(malwares_file_path).empty?
end
# return array of string (url of malwares found)
def malwares(malwares_file_path = nil)
unless @malwares
malwares_found = []
malwares_file = Malwares.malwares_file(malwares_file_path)
index_page_body = Browser.get(@uri.to_s).body
File.open(malwares_file, 'r') do |file|
file.readlines.collect do |url|
chomped_url = url.chomp
if chomped_url.length > 0
malwares_found += index_page_body.scan(Malwares.malware_pattern(chomped_url))
end
end
end
malwares_found.flatten!
malwares_found.uniq!
@malwares = malwares_found
end
@malwares
end
def self.malwares_file(malwares_file_path)
malwares_file_path || DATA_DIR + '/malwares.txt'
end
def self.malware_pattern(url_regex)
# no need to escape regex here, because malware.txt contains regex
%r{<(?:script|iframe).* src=(?:"|')(#{url_regex}[^"']*)(?:"|')[^>]*>}i
end
end
end

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

@@ -14,7 +14,7 @@ class WpTarget < WebSite
queue_count = 0
backups.each do |file|
file_url = @uri.merge(URI.escape(file)).to_s
file_url = @uri.merge(url_encode(file)).to_s
request = browser.forge_request(file_url)
request.on_complete do |response|
@@ -40,7 +40,7 @@ class WpTarget < WebSite
# @return [ Array ]
def self.config_backup_files
%w{
wp-config.php~ #wp-config.php# wp-config.php.save .wp-config.php.swp wp-config.php.swp wp-config.php.swo
wp-config.php~ #wp-config.php# wp-config.php.save .wp-config.php.swp wp-config.php.swp wp-config.php.swo
wp-config.php_bak wp-config.bak wp-config.php.bak wp-config.save wp-config.old wp-config.php.old
wp-config.php.orig wp-config.orig wp-config.php.original wp-config.original wp-config.txt
} # thanks to Feross.org for these

View File

@@ -23,9 +23,9 @@ class WpTarget < WebSite
# @return [ Boolean ]
def default_wp_content_dir_exists?
response = Browser.get(@uri.merge('wp-content').to_s)
hash = Digest::MD5.hexdigest(response.body)
if WpTarget.valid_response_codes.include?(response.code)
hash = WebSite.page_hash(response)
return true if hash != error_404_hash and hash != homepage_hash
end

View File

@@ -2,19 +2,21 @@
class WpTarget < WebSite
module WpFullPathDisclosure
# Check for Full Path Disclosure (FPD)
#
# @return [ Boolean ]
def has_full_path_disclosure?
response = Browser.get(full_path_disclosure_url())
response.body[%r{Fatal error}i] ? true : false
Browser.get(full_path_disclosure_url).body[%r/Fatal error/i] ? true : false
end
def full_path_disclosure_data
return nil unless has_full_path_disclosure?
Browser.get(full_path_disclosure_url).body[/Fatal error:.+? in (.+?) on/i, 1]
end
# @return [ String ]
def full_path_disclosure_url
@uri.merge('wp-includes/rss-functions.php').to_s
end
end
end

View File

@@ -8,7 +8,7 @@ class WpTarget < WebSite
@login_protection_plugin = nil
def has_login_protection?
!login_protection_plugin().nil?
!login_protection_plugin.nil?
end
# Checks if a login protection plugin is enabled
@@ -74,7 +74,7 @@ class WpTarget < WebSite
# http://wordpress.org/extend/plugins/login-security-solution/
def has_login_security_solution_protection?
Browser.get(login_security_solution_url()).code != 404
Browser.get(login_security_solution_url).code != 404
end
def login_security_solution_url
@@ -99,5 +99,12 @@ class WpTarget < WebSite
plugin_url('bluetrait-event-viewer')
end
# https://wordpress.org/plugins/security-protection/
def has_security_protection_protection?
Nokogiri::HTML(Browser.get(login_url).body).css('script').each do |node|
return true if node['src'] =~ /security-protection.js/i
end
false
end
end
end

View File

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

View File

@@ -10,7 +10,7 @@ class WpTarget < WebSite
#
# @return [ Boolean ]
def has_readme?
response = Browser.get(readme_url())
response = Browser.get(readme_url)
unless response.code == 404
return response.body =~ %r{wordpress}i ? true : false

View File

@@ -0,0 +1,68 @@
# 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?
# Read in RSS/XML
xml = Nokogiri::XML(data)
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

@@ -1,6 +1,6 @@
# encoding: UTF-8
require File.expand_path(File.dirname(__FILE__) + '/../common/common_helper')
require File.expand_path(File.join(__dir__, '..', 'common', 'common_helper'))
require_files_from_directory(WPSCAN_LIB_DIR, '**/*.rb')
@@ -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 ...'
@@ -62,7 +65,7 @@ def help
puts
puts 'Some values are settable in a config file, see the example.conf.json'
puts
puts '--update Update to the database to the latest version.'
puts '--update Update the database to the latest version.'
puts '--url | -u <target url> The WordPress URL/domain to scan.'
puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
puts '--enumerate | -e [option(s)] Enumeration.'
@@ -84,12 +87,17 @@ def help
puts ' You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).'
puts '--config-file | -c <config file> Use the specified config file, see the example.conf.json.'
puts '--user-agent | -a <User-Agent> Use the specified User-Agent.'
puts '--cookie <String> String to read cookies from.'
puts '--cookie <string> String to read cookies from.'
puts '--random-agent | -r Use a random User-Agent.'
puts '--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not'
puts '--batch Never ask for user input, use the default behaviour.'
puts '--no-color Do not use colors in the output.'
puts '--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it.'
puts '--log [filename] Creates a log.txt file with WPScan\'s output if no filename is supplied. Otherwise the filename is used for logging.'
puts '--no-banner Prevents the WPScan banner from being displayed.'
puts '--disable-accept-header Prevents WPScan sending the Accept HTTP header.'
puts '--disable-referer Prevents setting the Referer header.'
puts '--disable-tls-checks Disables SSL/TLS certificate verification.'
puts '--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specify it.'
puts ' Subdirectories are allowed.'
puts '--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory.'
puts ' If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed'
@@ -97,22 +105,89 @@ def help
puts ' If no protocol is given (format host:port), HTTP will be used.'
puts '--proxy-auth <username:password> Supply the proxy login credentials.'
puts '--basic-auth <username:password> Set the HTTP Basic authentication.'
puts '--wordlist | -w <wordlist> Supply a wordlist for the password bruter and do the brute.'
puts '--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.'
puts '--username | -U <username> Only brute force the supplied username.'
puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests.'
puts '--usernames <path-to-file> Only brute force the usernames from the file.'
puts '--cache-dir <cache-directory> Set the cache directory.'
puts '--cache-ttl <cache-ttl> Typhoeus cache TTL.'
puts '--request-timeout <request-timeout> Request Timeout.'
puts '--connect-timeout <connect-timeout> Connect Timeout.'
puts '--max-threads <max-threads> Maximum Threads.'
puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests.'
puts '--throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.'
puts '--help | -h This help screen.'
puts '--verbose | -v Verbose output.'
puts '--version Output the current version and exit.'
puts
end
# Hook to check if the target if down during the scan
# The target is considered down after 10 requests with status = 0
down = 0
Typhoeus.on_complete do |response|
down += 1 if response.code == 0
fail 'The target seems to be down' if down >= 10
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
down = 0
@total_requests_done = 0
Typhoeus.on_complete do |response|
next if response.cached?
down += 1 if response.code == 0
@total_requests_done += 1
fail 'The target seems to be down' if down >= 30
next unless Browser.instance.throttle > 0
sleep(Browser.instance.throttle)
end

View File

@@ -1,7 +1,6 @@
# encoding: UTF-8
class WpscanOptions
ACCESSOR_OPTIONS = [
:batch,
:enumerate_plugins,
@@ -14,15 +13,18 @@ class WpscanOptions
:enumerate_usernames,
:enumerate_usernames_range,
:no_color,
:log,
:proxy,
:proxy_auth,
:threads,
:url,
:vhost,
:wordlist,
:force,
:update,
:verbose,
:username,
:usernames,
:password,
:follow_redirection,
:wp_content_dir,
@@ -39,7 +41,13 @@ class WpscanOptions
:cache_ttl,
:request_timeout,
:connect_timeout,
:max_threads
:max_threads,
:no_banner,
:throttle,
:disable_accept_header,
:disable_referer,
:cache_dir,
:disable_tls_checks
]
attr_accessor *ACCESSOR_OPTIONS
@@ -51,23 +59,35 @@ class WpscanOptions
end
def url=(url)
raise 'Empty URL given' if !url
raise Exception.new('Empty URL given') if url.nil? || url == ''
url = Addressable::URI.parse(url).normalize.to_s unless url.ascii_only?
@url = URI.parse(add_http_protocol(url)).to_s
end
def vhost=(vhost)
@vhost = vhost
end
def threads=(threads)
@threads = threads.is_a?(Integer) ? threads : threads.to_i
end
def wordlist=(wordlist)
if File.exists?(wordlist)
if File.exists?(wordlist) || wordlist == '-'
@wordlist = wordlist
else
raise "The file #{wordlist} does not exist"
end
end
def usernames=(file)
fail "The file #{file} does not exist" unless File.exists?(file)
@usernames = file
end
def proxy=(proxy)
if proxy.index(':') == nil
raise 'Invalid proxy format. Should be host:port.'
@@ -132,11 +152,6 @@ class WpscanOptions
end
end
def basic_auth=(basic_auth)
raise 'Invalid basic authentication format, login:password expected' if basic_auth.index(':').nil?
@basic_auth = "Basic #{Base64.encode64(basic_auth).chomp}"
end
def debug_output=(debug_output)
Typhoeus::Config.verbose = debug_output
end
@@ -192,7 +207,9 @@ class WpscanOptions
enumerate_options_from_string(cli_value)
else
raise "Unknow option : #{cli_option} with value #{cli_value}"
text = "Unknown option : #{cli_option}"
text << " with value #{cli_value}" if (cli_value && !cli_value.empty?)
raise text
end
end
@@ -235,8 +252,10 @@ class WpscanOptions
def self.get_opt_long
GetoptLong.new(
['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
['--vhost',GetoptLong::OPTIONAL_ARGUMENT],
['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT],
['--username', '-U', GetoptLong::REQUIRED_ARGUMENT],
['--usernames', GetoptLong::REQUIRED_ARGUMENT],
['--wordlist', '-w', GetoptLong::REQUIRED_ARGUMENT],
['--threads', '-t', GetoptLong::REQUIRED_ARGUMENT],
['--force', '-f', GetoptLong::NO_ARGUMENT],
@@ -258,10 +277,16 @@ 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]
['--cookie', GetoptLong::REQUIRED_ARGUMENT],
['--log', GetoptLong::OPTIONAL_ARGUMENT],
['--no-banner', GetoptLong::NO_ARGUMENT],
['--throttle', GetoptLong::REQUIRED_ARGUMENT],
['--disable-accept-header', GetoptLong::NO_ARGUMENT],
['--disable-referer', GetoptLong::NO_ARGUMENT],
['--cache-dir', GetoptLong::REQUIRED_ARGUMENT],
['--disable-tls-checks', GetoptLong::NO_ARGUMENT],
)
end

View File

@@ -1,138 +0,0 @@
# encoding: UTF-8
class CheckerPlugin < Plugin
def initialize
super(author: 'WPScanTeam - @erwanlr')
register_options(
['--check-vuln-ref-urls', '--cvru', 'Check all the vulnerabilities reference urls for 404'],
['--check-local-vulnerable-files LOCAL_DIRECTORY', '--clvf', 'Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells']
)
end
def run(options = {})
if options[:check_vuln_ref_urls]
check_vuln_ref_urls
end
if options[:check_local_vulnerable_files]
check_local_vulnerable_files(options[:check_local_vulnerable_files])
end
end
def check_vuln_ref_urls
vuln_ref_files = [PLUGINS_VULNS_FILE, THEMES_VULNS_FILE, WP_VULNS_FILE]
error_codes = [404, 500, 403]
not_found_regexp = %r{No Results Found|error 404|ID Invalid or Not Found}i
puts '[+] Checking vulnerabilities reference urls'
vuln_ref_files.each do |vuln_ref_file|
json = json(vuln_ref_file)
urls = []
json.each do |asset|
asset[asset.keys.inject]['vulnerabilities'].each do |url|
unless url['url'].nil?
url['url'].split(',').each do |url|
urls << url
end
end
end
end
urls.uniq!
puts "[!] No URLs found in #{vuln_ref_file}!" if urls.empty?
dead_urls = []
queue_count = 0
request_count = 0
browser = Browser.instance
hydra = browser.hydra
number_of_urls = urls.size
urls.each do |url|
request = browser.forge_request(url, { cache_ttl: 0, followlocation: true })
request_count += 1
request.on_complete do |response|
print "\r [+] Checking #{vuln_ref_file} #{number_of_urls} total ... #{(request_count * 100) / number_of_urls}% complete."
if error_codes.include?(response.code) or not_found_regexp.match(response.body)
dead_urls << url
end
end
hydra.queue(request)
queue_count += 1
if queue_count == browser.max_threads
hydra.run
queue_count = 0
end
end
hydra.run
puts
unless dead_urls.empty?
dead_urls.each { |url| puts " Not Found #{url}" }
end
end
end
def check_local_vulnerable_files(dir_to_scan)
if Dir.exist?(dir_to_scan)
xml_file = LOCAL_FILES_FILE
local_hashes = {}
file_extension_to_scan = '*.{js,php,swf,html,htm}'
print '[+] Generating local hashes ... '
Dir[File.join(dir_to_scan, '**', file_extension_to_scan)]
.select { |f| File.file?(f) }
.each do |filename|
sha1sum = Digest::SHA1.file(filename).hexdigest
if local_hashes.key?(sha1sum)
local_hashes[sha1sum] << filename
else
local_hashes[sha1sum] = [filename]
end
end
puts 'done.'
puts '[+] Checking for vulnerable files ...'
xml = xml(xml_file)
xml.xpath('//hash').each do |node|
sha1sum = node.attribute('sha1').text
if local_hashes.has_key?(sha1sum)
local_filenames = local_hashes[sha1sum]
vuln_title = node.search('title').text
vuln_filename = node.search('file').text
vuln_refrence = node.search('reference').text
puts " #{vuln_filename} found :"
puts ' | Location(s):'
local_filenames.each do |file|
puts " | - #{file}"
end
puts ' |'
puts " | Title: #{vuln_title}"
puts " | Refrence: #{vuln_refrence}" if !vuln_refrence.empty?
puts
end
end
puts 'done.'
else
puts "The supplied directory '#{dir_to_scan}' does not exist"
end
end
end

View File

@@ -1,91 +0,0 @@
# encoding: UTF-8
class CheckerSpelling < Plugin
def initialize
super(author: 'WPScanTeam - @ethicalhack3r')
register_options(['--spellcheck', '--sc', 'Check all files for common spelling mistakes.'])
end
def run(options = {})
spellcheck if options[:spellcheck]
end
def spellcheck
mistakes = 0
puts '[+] Checking for spelling mistakes'
puts
files.each do |file_name|
if File.exists?(file_name)
file = File.open(file_name, 'r')
misspellings.each_key do |misspelling|
begin
file.read.scan(/#{misspelling}/).each do |match|
mistakes += 1
puts "[MISSPELLING] File: #{file_name} Bad: #{match} Good: #{misspellings[misspelling]}"
end
rescue => e
puts "Error in #{file_name} #{e}"
next
end
end
file.close
end
end
puts
puts "[+] Found #{mistakes} spelling mistakes"
mistakes
end
def misspellings
{
/databse/i => 'database',
/whith/i => 'with',
/wich/i => 'which',
/verions/i => 'versions',
/vulnerabilitiy/i => 'vulnerability',
/unkown/i => 'unknown',
/recieved/i => 'received',
/acheive/i => 'achieve',
/wierd/i => 'weird',
/untill/i => 'until',
/alot/i => 'a lot',
/randomstorm/ => 'RandomStorm',
/wpscan/ => 'WPScan',
/Wordpress/ => 'WordPress'
}
end
def files
files = Dir['**/*'].reject {|fn| File.directory?(fn) }
ignore.each do |ignore|
files.delete_if { |data| data.match(ignore) }
end
files
end
def ignore
ignore = []
ignore << File.basename(__FILE__)
ignore << 'spec/cache/'
ignore << 'spec/spec_session/'
ignore << 'cache/'
ignore << 'coverage/'
ignore << 'wordlist-iso-8859-1'
ignore << 'log.txt'
ignore << 'debug.log'
ignore << 'wordlist.txt'
ignore
end
end

View File

@@ -1,106 +0,0 @@
# encoding: UTF-8
class StatsPlugin < Plugin
def initialize
super(author: 'WPScanTeam - Christian Mehlmauer')
register_options(
['--stats', '-s', 'Show WpScan Database statistics.']
)
end
def run(options = {})
if options[:stats]
date_wp = File.mtime(WP_VULNS_FILE)
date_plugins = File.mtime(PLUGINS_VULNS_FILE)
date_themes = File.mtime(THEMES_VULNS_FILE)
date_plugins_full = File.mtime(PLUGINS_FULL_FILE)
date_themes_full = File.mtime(THEMES_FULL_FILE)
puts "WPScan Database Statistics:"
puts "---------------------------"
puts
puts "[#] Total vulnerable versions: #{vuln_core_count}"
puts "[#] Total vulnerable plugins: #{vuln_plugin_count}"
puts "[#] Total vulnerable themes: #{vuln_theme_count}"
puts
puts "[#] Total version vulnerabilities: #{version_vulns_count}"
puts "[#] Total fixed vulnerabilities: #{fix_version_count}"
puts
puts "[#] Total plugin vulnerabilities: #{plugin_vulns_count}"
puts "[#] Total fixed vulnerabilities: #{fix_plugin_count}"
puts
puts "[#] Total theme vulnerabilities: #{theme_vulns_count}"
puts "[#] Total fixed vulnerabilities: #{fix_theme_count}"
puts
puts "[#] Total plugins to enumerate: #{total_plugins}"
puts "[#] Total themes to enumerate: #{total_themes}"
puts
puts "[+] WordPress DB modified: #{date_wp.strftime('%Y-%m-%d %H:%M:%S')}"
puts "[+] Plugins DB modified: #{date_plugins.strftime('%Y-%m-%d %H:%M:%S')}"
puts "[+] Themes DB modified: #{date_themes.strftime('%Y-%m-%d %H:%M:%S')}"
puts "[+] Enumeration plugins: #{date_plugins_full.strftime('%Y-%m-%d %H:%M:%S')}"
puts "[+] Enumeration themes: #{date_themes_full.strftime('%Y-%m-%d %H:%M:%S')}"
puts
puts "[+] Report generated: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
end
end
def vuln_core_count(file=WP_VULNS_FILE)
json(file).size
end
def vuln_plugin_count(file=PLUGINS_VULNS_FILE)
json(file).size
end
def vuln_theme_count(file=THEMES_VULNS_FILE)
json(file).size
end
def version_vulns_count(file=WP_VULNS_FILE)
asset_vulns_count(json(file))
end
def fix_version_count(file=WP_VULNS_FILE)
asset_fixed_in_count(json(file))
end
def plugin_vulns_count(file=PLUGINS_VULNS_FILE)
asset_vulns_count(json(file))
end
def fix_plugin_count(file=PLUGINS_VULNS_FILE)
asset_fixed_in_count(json(file))
end
def theme_vulns_count(file=THEMES_VULNS_FILE)
asset_vulns_count(json(file))
end
def fix_theme_count(file=THEMES_VULNS_FILE)
asset_fixed_in_count(json(file))
end
def total_plugins(file=PLUGINS_FULL_FILE)
lines_in_file(file)
end
def total_themes(file=THEMES_FULL_FILE)
lines_in_file(file)
end
def lines_in_file(file)
IO.readlines(file).size
end
def asset_vulns_count(json)
json.map { |asset| asset[asset.keys.inject]['vulnerabilities'].size }.inject(:+)
end
def asset_fixed_in_count(json)
json.map { |asset| asset[asset.keys.inject]['vulnerabilities'].map {|a| a['fixed_in'].nil? ? 0 : 1 }.inject(:+) }.inject(:+)
end
end

View File

@@ -1,20 +0,0 @@
# encoding: UTF-8
require File.expand_path(File.dirname(__FILE__) + '/../common/common_helper')
require_files_from_directory(WPSTOOLS_LIB_DIR)
require_files_from_directory(WPSTOOLS_PLUGINS_DIR, '**/*.rb')
def usage
script_name = $0
puts
puts '-h for further help.'
puts
puts 'Examples:'
puts
puts 'Locally scan a wordpress installation for vulnerable files or shells'
puts "ruby #{script_name} --check-local-vulnerable-files /var/www/wordpress/"
puts
puts 'See README for further information.'
puts
end

View File

@@ -64,7 +64,7 @@ describe Browser do
it 'raises an error' do
File.symlink('./testfile', config_file)
expect { browser.load_config(config_file) }.to raise_error("[ERROR] Config file is a symlink.")
expect { browser.load_config(config_file) }.to raise_error('[ERROR] Config file is a symlink.')
File.unlink(config_file)
end
end
@@ -124,29 +124,36 @@ describe Browser do
describe '#merge_request_params' do
let(:params) { {} }
let(:cookie_jar) { CACHE_DIR + '/browser/cookie-jar' }
let(:user_agent) { 'SomeUA' }
let(:default_expectation) {
{
cache_ttl: 250,
headers: { 'User-Agent' => 'SomeUA' },
ssl_verifypeer: false, ssl_verifyhost: 0,
cookiejar: cookie_jar, cookiefile: cookie_jar,
timeout: 2000, connecttimeout: 1000,
timeout: 60, connecttimeout: 10,
maxredirs: 3,
referer: nil
}
}
after :each do
browser.user_agent = 'SomeUA'
browser.user_agent = user_agent
browser.cache_ttl = 250
expect(browser.merge_request_params(params)).to eq @expected
expect(Typhoeus::Config.user_agent).to eq user_agent
end
it 'sets the User-Agent header field and cache_ttl' do
@expected = default_expectation
end
context 'when @user_agent' do
let(:user_agent) { 'test' }
it 'sets the User-Agent' do
@expected = default_expectation
end
end
context 'when @proxy' do
let(:proxy) { '127.0.0.1:9050' }
@@ -161,16 +168,24 @@ 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
context 'when @request_timeout' do
it 'gives an Integer' do
browser.request_timeout = '10'
@expected = default_expectation.merge(timeout: 10)
end
end
context 'when @basic_auth' do
it 'appends the basic_auth' do
browser.basic_auth = 'user:pass'
@expected = default_expectation.merge(
headers: default_expectation[:headers].merge('Authorization' => 'Basic '+Base64.encode64('user:pass').chomp)
headers: { 'Authorization' => 'Basic ' + Base64.encode64('user:pass').chomp }
)
end
end
@@ -199,6 +214,13 @@ describe Browser do
@expected = default_expectation.merge(cookie: cookie)
end
end
context 'when @disable_tls_checks' do
it 'disables tls checks' do
browser.disable_tls_checks = true
@expected = default_expectation.merge(ssl_verifypeer: 0, ssl_verifyhost: 0)
end
end
end
describe '#forge_request' do

View File

@@ -30,14 +30,15 @@ describe CacheFileStore do
describe '#clean' do
it "should remove all files from the cache dir (#{@cache_dir}" do
# let's create some files into the directory first
(0..5).each do |i|
File.new(@cache.storage_path + "/file_#{i}.txt", File::CREAT)
end
expect(count_files_in_dir(@cache.storage_path, 'file_*.txt')).to eq 6
# clean is executed by other tests before
before = count_files_in_dir(@cache.cache_dir)
test_dir = File.expand_path("#{@cache.cache_dir}/test")
Dir.mkdir test_dir
#change the modification date
%x[ touch -t 200701310846.26 #{test_dir} ]
expect(count_files_in_dir(@cache.cache_dir)).to eq (before + 1)
@cache.clean
expect(count_files_in_dir(@cache.storage_path)).to eq 0
expect(count_files_in_dir(@cache.cache_dir)).to eq before
end
end
@@ -91,7 +92,7 @@ describe CacheFileStore do
it 'should create a unique storage dir' do
storage_dirs = []
(1..5).each do |i|
(1..5).each do |_|
storage_dirs << CacheFileStore.new(cache_dir).storage_path
end

View File

@@ -18,7 +18,7 @@ describe WpItems do
vulnerable_targets_items: [ WpItem.new(uri, name: 'mr-smith'),
WpItem.new(uri, name: 'neo')],
passive_detection: (1..13).reduce(WpItems.new) { |o, i| o << WpItem.new(uri, name: "detect-me-#{i}") }
passive_detection: (1..15).reduce(WpItems.new) { |o, i| o << WpItem.new(uri, name: "detect-me-#{i}") }
}
end
end

View File

@@ -100,6 +100,13 @@ describe 'WpPlugins::Detectable' do
expected.add('all-in-one-seo-pack', version: '2.0.3.1')
end
end
context 'when google-universal-analytics detected' do
it 'returns google-universal-analytics' do
@body = '<!-- Google Universal Analytics for WordPress v2.4.2 -->'
expected.add('google-universal-analytics', version: '2.4.2')
end
end
end
end

View File

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

View File

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

View File

@@ -26,10 +26,10 @@ describe 'WpTimthumbs::Detectable' do
def expected_targets_from_theme(theme_name)
expected = []
%w{
%w(
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
}.each do |file|
scripts/timthumb.php tools/timthumb.php functions/timthumb.php thumb.php
).each do |file|
path = "$wp-content$/themes/#{theme_name}/#{file}"
expected << WpTimthumb.new(uri, path: path)
end
@@ -46,7 +46,7 @@ describe 'WpTimthumbs::Detectable' do
after do
targets = subject.send(:targets_items_from_file, file, wp_target)
expect(targets.map { |t| t.url }).to eq @expected.map { |t| t.url }
expect(targets.map(&:url)).to eq @expected.map(&:url)
end
context 'when an empty file' do
@@ -71,7 +71,7 @@ describe 'WpTimthumbs::Detectable' do
theme = 'hello-world'
targets = subject.send(:theme_timthumbs, theme, wp_target)
expect(targets.map { |t| t.url }).to eq expected_targets_from_theme(theme).map { |t| t.url }
expect(targets.map(&:url)).to eq expected_targets_from_theme(theme).map(&:url)
end
end
@@ -81,7 +81,7 @@ describe 'WpTimthumbs::Detectable' do
after do
targets = subject.send(:targets_items, wp_target, options)
expect(targets.map { |t| t.url }).to eq @expected.sort.map { |t| t.url }
expect(targets.map(&:url)).to match_array(@expected.map(&:url))
end
context 'when no :theme_name' do
@@ -101,7 +101,7 @@ describe 'WpTimthumbs::Detectable' do
end
context 'when :theme_name' do
let(:theme) { 'theme-name'}
let(:theme) { 'theme-name' }
context 'when no :file' do
let(:options) { { theme_name: theme } }

Some files were not shown because too many files have changed in this diff Show More