Compare commits

...

223 Commits
2.6 ... 2.9.1

Author SHA1 Message Date
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
139 changed files with 6797 additions and 3017 deletions

View File

@@ -1 +1 @@
2.1.5 2.3.1

View File

@@ -1,18 +1,23 @@
language: ruby language: ruby
sudo: false
cache: bundler
rvm: rvm:
- 1.9.2 # Still not in Travis :(
- 1.9.3 # - 2.1.9
- 2.0.0 - 2.2.0
- 2.1.0 - 2.2.1
- 2.1.1 - 2.2.2
- 2.1.2 - 2.2.3
- 2.1.3 - 2.2.4
- 2.1.4 - 2.3.0
- 2.1.5 - 2.3.1
before_install:
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
script: bundle exec rspec script: bundle exec rspec
notifications: notifications:
email: email:
- wpscanteam@gmail.com - team@wpscan.org
matrix: # do not build gh-pages branch
allow_failures: branches:
- rvm: 1.9.2 except:
- gh-pages

View File

@@ -1,6 +1,147 @@
# Changelog # Changelog
## Master ## Master
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.6...master) [Work in progress](https://github.com/wpscanteam/wpscan/compare/2.9.1...master)
## Version 2.9.1
Released: 2016-05-06
* Update to Ruby 2.3.1, drop older ruby support
* New data file location
* Added experimental Windows support
* Display WordPress metadata on the detected version
* Several small fixes
WPScan Database Statistics:
* Total vulnerable versions: 156
* Total vulnerable plugins: 1324
* Total vulnerable themes: 376
* Total version vulnerabilities: 1998
* Total plugin vulnerabilities: 2057
* Total theme vulnerabilities: 449
## Version 2.9
Released: 2015-10-15
New
* GZIP Encoding in updater
* Adds --throttle option to throttle requests
* Uses new API and local database file structure
* Adds last updated and latest version to plugins and themes
Removed
* ArchAssault from README
* APIv1 local databases
General core
* Update to Ruby 2.2.3
* Use yajl-ruby as JSON parser
* New dependancy for Ubuntu 14.04 (libgmp-dev)
* Use Travis container based infra and caching
Fixed issues
* Fix #835 - Readme requests to wp root dir
* Fix #836 - Critical icon output twice when the site is not running WP
* Fix #839 - Terminal-table dependency is broken
* Fix #841 - error: undefined method `cells' for #<Array:0x000000029cc2f8>
* Fix #852 - GZIP Encoding in updater
* Fix #853 - APIv2 integration
* Fix #858 - Detection FP
* Fix #873 - false positive "site has Must Use Plugins"
WPScan Database Statistics:
* Total vulnerable versions: 132
* Total vulnerable plugins: 1170
* Total vulnerable themes: 368
* Total version vulnerabilities: 1476
* Total plugin vulnerabilities: 1913
* Total theme vulnerabilities: 450
## Version 2.8
Released: 2015-06-22
New
* Warn the user to update his DB files
* Added last db update to --version option (see #815)
* Add db checksum to verbose logging during update
* Option to hide banner
* Continue if user chooses not to update + db exists
* Don't update if user chooses default + no DBs exist
* Updates request timeout values to realistic ones (and in seconds)
Removed
* Removed `Time.parse('2000-01-01')` expedient
* Removed unnecessary 'return' and '()'
* Removed debug output
* Removed wpstools
General core
* Update to Ruby 2.2.2
* Switch to mitre
* Install bundler gem README
* Switch from gnutls to openssl
Fixed issues
* Fix #789 - Add blackarch to readme
* Fix #790 - Consider the target down after 30 requests timed out requests instead of 10
* Fix #791 - Rogue character causing the scan of non-wordpress site to crash
* Fix #792 - Adds the HttpError exception
* Fix #795 - Remove GHOST warning
* Fix #796 - Do not swallow exit code
* Fix #797 - Increases the timeout values
* Fix #801 - Forces UTF-8 encoding when enumerating usernames
* Fix #803 - Increases default connect-timeout to 10s
* Fix #804 - Updates the Theme detection pattern
* Fix #816 - Ignores potential non version chars in theme version detection
* Fix #819 - Removes potential spaces in robots.txt entries
WPScan Database Statistics:
* Total vulnerable versions: 98
* Total vulnerable plugins: 1076
* Total vulnerable themes: 361
* Total version vulnerabilities: 1104
* Total plugin vulnerabilities: 1763
* Total theme vulnerabilities: 443
## Version 2.7
Released: 2015-03-16
New
* Detects version in release date format
* Copyrights updated
* WP version detection from stylesheets
* New license
* Global HTTP request counter
* Add security-protection plugin detection
* Add GHOST warning if XMLRPC enabled
* Update databases from wpvulndb.com
* Enumerate usernames from WP <= 3.0 (thanks berotti3)
Removed
* README.txt
General core
* Update to Ruby 2.2.1
* Update to Ruby 2.2.0
* Add addressable gem
* Update Typhoeus gem to 0.7.0
* IDN support: encode non-ascii domain names (thanks dctabuyz)
* Improve page hash calculation (thanks dctabuyz)
* Version detection regex improved
Fixed issues
* Fix #745 - Plugin version pattern in readme.txt file not detected
* Fix #746 - Add a global counter for all active requests to server.
* Fix #747 - Add 'security-protection' plugin to wp_login_protection module
* Fix #753 - undefined method `round' for "10":String for request or connect timeouts
* Fix #760 - typhoeus issue (infinite loop)
WPScan Database Statistics:
* Total vulnerable versions: 89
* Total vulnerable plugins: 953
* Total vulnerable themes: 329
* Total version vulnerabilities: 1070
* Total plugin vulnerabilities: 1451
* Total theme vulnerabilities: 378
## Version 2.6 ## Version 2.6
Released: 2014-12-19 Released: 2014-12-19
@@ -13,7 +154,7 @@ New
* Add Sucuri sponsor to banner * Add Sucuri sponsor to banner
* Add protocol to sucuri url in banner * Add protocol to sucuri url in banner
* Add response code to proxy error output * Add response code to proxy error output
* Add a statement about mendatory newlines at the end of list * Add a statement about mandatory newlines at the end of list
* Give warning if default username 'admin' is still used * Give warning if default username 'admin' is still used
* License amendment to make it more clear about value added usage * License amendment to make it more clear about value added usage
@@ -369,4 +510,3 @@ Fixed issues
## Version 2.1 ## Version 2.1
Released 2013-3-4 Released 2013-3-4

View File

@@ -1,12 +1,12 @@
**CREDITS** **CREDITS**
This file is to give credit to WPScan's contributors. If you feel your name should be in here, email ryandewhurst at gmail. This file is used to state the individual WPScan Team members (core developers) and give credit to WPScan's other contributors. If you feel your name should be in here email team@wpscan.org.
*WPScan Team* *WPScan Team*
Erwan.LR - @erwan_lr - (Project Developer) Erwan.LR - @erwan_lr - (Project Developer)
Christian Mehlmauer - @_FireFart_ - (Project Developer) Christian Mehlmauer - @_FireFart_ - (Project Developer)
Peter van der Laan - pvdl - (Vuln Hunter and Code Cleaner) Peter van der Laan - pvdl - (Project Developer)
Ryan Dewhurst - @ethicalhack3r (Project Lead) Ryan Dewhurst - @ethicalhack3r (Project Lead)
*Other Contributors* *Other Contributors*
@@ -18,4 +18,4 @@ Callum Pember - Implemented proxy support - callumpember at gmail.com
g0tmi1k - Additional timthumb checks + bug reports g0tmi1k - Additional timthumb checks + bug reports
Melvin Lammerts - Reported a couple of fake vulnerabilities - melvin at 12k.nl Melvin Lammerts - Reported a couple of fake vulnerabilities - melvin at 12k.nl
Paolo Perego - @thesp0nge - Basic authentication Paolo Perego - @thesp0nge - Basic authentication
Gianluca Brindisi - @gbrindisi - Project Developer Gianluca Brindisi - @gbrindisi - Ex Project Developer

14
Gemfile
View File

@@ -1,14 +1,18 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'typhoeus', '~>0.6.8' gem 'typhoeus', '>=0.8.0'
gem 'nokogiri' gem 'nokogiri', '>=1.6.7.1'
gem 'json' gem 'addressable'
gem 'terminal-table' gem 'yajl-ruby' # Better JSON parser regarding memory usage
# TODO: update the below when terminal-table 1.5.3+ is released.
# See issue #841 for details
# (and delete the Terminal module in lib/common/hacks.rb)
gem 'terminal-table', '~>1.4.5'
gem 'ruby-progressbar', '>=1.6.0' gem 'ruby-progressbar', '>=1.6.0'
group :test do group :test do
gem 'webmock', '>=1.17.2' gem 'webmock', '>=1.17.2'
gem 'simplecov' gem 'simplecov'
gem 'rspec', '>=3.0' gem 'rspec', '>=3.3.0'
gem 'rspec-its' gem 'rspec-its'
end end

75
LICENSE
View File

@@ -1,21 +1,70 @@
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team. WPScan Public Source License
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License. The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2016 WPScan Team.
Cases of commercialization are: Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
- Using WPScan to provide commercial managed/Software-as-a-Service services. 1. Definitions
- Distributing WPScan as a commercial product or as part of one.
- Using WPScan as a value added service/product.
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, an updated list of whom can be found within the CREDITS file.
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause. 2. Commercialization
- 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. 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.

292
README
View File

@@ -1,292 +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.
- Using WPScan as a value added service/product.
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.4
* Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
* RubyGems - Recommended: latest
* Git
Windows is not supported.
If installed from Github update the code base with git pull. The databases are updated with wpscan.rb --update.
-> Installing on Ubuntu:
Before Ubuntu 14.04:
sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
git clone https://github.com/wpscanteam/wpscan.git
cd wpscan
sudo gem install bundler && bundle install --without test
From Ubuntu 14.04:
sudo apt-get install libcurl4-gnutls-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential
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.4
rvm use 2.1.4 --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 brute forcer.
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
--username | -U <username> Only brute force the supplied username.
--usernames <path-to-file> Only brute force the usernames from the file.
--cache-ttl <cache-ttl> Typhoeus cache TTL.
--request-timeout <request-timeout> Request Timeout.
--connect-timeout <connect-timeout> Connect Timeout.
--max-threads <max-threads> Maximum Threads.
--help | -h This help screen.
--verbose | -v Verbose output.
--batch Never ask for user input, use the default behaviour.
--no-color Do not use colors in the output.
--log Save STDOUT to log.txt
==WPSCAN EXAMPLES==
Do 'non-intrusive' checks...
ruby wpscan.rb --url www.example.com
Do wordlist password brute force on enumerated users using 50 threads...
ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --threads 50
Do wordlist password brute force on the 'admin' username only...
ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --username admin
Enumerate installed plugins...
ruby wpscan.rb --url www.example.com --enumerate p
Run all enumeration tools...
ruby wpscan.rb --url www.example.com --enumerate
Use custom content directory...
ruby wpscan.rb -u www.example.com --wp-content-dir custom-content
Update WPScan's databases...
ruby wpscan.rb --update
Debug output...
ruby wpscan.rb --url www.example.com --debug-output 2>debug.log
==WPSTOOLS ARGUMENTS==
-v, --verbose Verbose output
--check-vuln-ref-urls, --cvru Check all the vulnerabilities reference urls for 404
--check-local-vulnerable-files, --clvf LOCAL_DIRECTORY Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells
s, --stats Show WpScan Database statistics.
--spellcheck, --sc Check all files for common spelling mistakes.
==WPSTOOLS EXAMPLES==
Locally scan a wordpress installation for vulnerable files or shells:
ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/
===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
===SPECIAL THANKS===
RandomStorm - https://www.randomstorm.com

206
README.md
View File

@@ -1,33 +1,84 @@
![alt text](https://raw.githubusercontent.com/wpscanteam/wpscan/gh-pages/wpscan_logo_407x80.png "WPScan - WordPress Security Scanner") ![alt text](https://raw.githubusercontent.com/wpscanteam/wpscan/gh-pages/wpscan_logo_407x80.png "WPScan - WordPress Security Scanner")
[![Build Status](https://travis-ci.org/wpscanteam/CMSScanner.svg?branch=master)](https://travis-ci.org/wpscanteam/CMSScanner) [![Build Status](https://travis-ci.org/wpscanteam/wpscan.svg?branch=master)](https://travis-ci.org/wpscanteam/wpscan)
[![Code Climate](https://img.shields.io/codeclimate/github/wpscanteam/wpscan.svg)](https://codeclimate.com/github/wpscanteam/wpscan) [![Code Climate](https://img.shields.io/codeclimate/github/wpscanteam/wpscan.svg)](https://codeclimate.com/github/wpscanteam/wpscan)
[![Dependency Status](https://img.shields.io/gemnasium/wpscanteam/wpscan.svg)](https://gemnasium.com/wpscanteam/wpscan) [![Dependency Status](https://img.shields.io/gemnasium/wpscanteam/wpscan.svg)](https://gemnasium.com/wpscanteam/wpscan)
#### LICENSE #### LICENSE
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team. #### WPScan Public Source License
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License. The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2016 WPScan Team.
Cases of commercialization are: Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
- Using WPScan to provide commercial managed/Software-as-a-Service services. ##### 1. Definitions
- Distributing WPScan as a commercial product or as part of one.
- Using WPScan as a value added service/product.
Cases which do not require a commercial license, and thus fall under the terms of GNU General Public License, include (but are not limited to): 1.1 "License" means this document.
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause. 1.2 "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
- 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. 1.3 "WPScan Team" means WPScans core developers, an updated list of whom can be found within the CREDITS file.
wpscanteam at gmail.com ##### 2. Commercialization
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. A commercial use is one intended for commercial advantage or monetary compensation.
Example cases of commercialization are:
- Using WPScan to provide commercial managed/Software-as-a-Service services.
- Distributing WPScan as a commercial product or as part of one.
- Using WPScan as a value added service/product.
Example cases which do not require a commercial license, and thus fall under the terms set out below, include (but are not limited to):
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit.
- Penetration Testing Linux Distributions including but not limited to Kali Linux, SamuraiWTF, BackBox Linux.
- Using WPScan to test your own systems.
- Any non-commercial use of WPScan.
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
Free-use Terms and Conditions;
##### 3. Redistribution
Redistribution is permitted under the following conditions:
- Unmodified License is provided with WPScan.
- Unmodified Copyright notices are provided with WPScan.
- Does not conflict with the commercialization clause.
##### 4. Copying
Copying is permitted so long as it does not conflict with the Redistribution clause.
##### 5. Modification
Modification is permitted so long as it does not conflict with the Redistribution clause.
##### 6. Contributions
Any Contributions assume the Contributor grants the WPScan Team the unlimited, non-exclusive right to reuse, modify and relicense the Contributor's content.
##### 7. Support
WPScan is provided under an AS-IS basis and without any support, updates or maintenance. Support, updates and maintenance may be given according to the sole discretion of the WPScan Team.
##### 8. Disclaimer of Warranty
WPScan is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the WPScan is free of defects, merchantable, fit for a particular purpose or non-infringing.
##### 9. Limitation of Liability
To the extent permitted under Law, WPScan is provided under an AS-IS basis. The WPScan Team shall never, and without any limit, be liable for any damage, cost, expense or any other payment incurred as a result of WPScan's actions, failure, bugs and/or any other interaction between WPScan and end-equipment, computers, other software or any 3rd party, end-equipment, computer or services.
##### 10. Disclaimer
Running WPScan against websites without prior mutual consent may be illegal in your country. The WPScan Team accept no liability and are not responsible for any misuse or damage caused by WPScan.
#### INSTALL #### INSTALL
@@ -37,11 +88,11 @@ WPScan comes pre-installed on the following Linux distributions:
- [Kali Linux](http://www.kali.org/) - [Kali Linux](http://www.kali.org/)
- [Pentoo](http://www.pentoo.ch/) - [Pentoo](http://www.pentoo.ch/)
- [SamuraiWTF](http://samurai.inguardians.com/) - [SamuraiWTF](http://samurai.inguardians.com/)
- [ArchAssault](https://archassault.org/) - [BlackArch](http://blackarch.org/)
Prerequisites: Prerequisites:
- Ruby >= 1.9.2 - Recommended: 2.1.4 - Ruby >= 2.1.9 - Recommended: 2.3.1
- Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault - Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
- RubyGems - Recommended: latest - RubyGems - Recommended: latest
- Git - Git
@@ -53,21 +104,21 @@ If installed from Github update the code base with ```git pull```. The databases
Before Ubuntu 14.04: Before Ubuntu 14.04:
sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev sudo apt-get install libcurl4-openssl-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
git clone https://github.com/wpscanteam/wpscan.git git clone https://github.com/wpscanteam/wpscan.git
cd wpscan cd wpscan
sudo gem install bundler && bundle install --without test sudo gem install bundler && bundle install --without test
From Ubuntu 14.04: From Ubuntu 14.04:
sudo apt-get install libcurl4-gnutls-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential sudo apt-get install libcurl4-openssl-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential libgmp-dev
git clone https://github.com/wpscanteam/wpscan.git git clone https://github.com/wpscanteam/wpscan.git
cd wpscan cd wpscan
sudo gem install bundler && bundle install --without test sudo gem install bundler && bundle install --without test
####Installing on Debian: ####Installing on Debian:
sudo apt-get install git ruby ruby-dev libcurl4-gnutls-dev make sudo apt-get install git ruby ruby-dev libcurl4-openssl-dev make zlib1g-dev
git clone https://github.com/wpscanteam/wpscan.git git clone https://github.com/wpscanteam/wpscan.git
cd wpscan cd wpscan
sudo gem install bundler sudo gem install bundler
@@ -75,7 +126,7 @@ From Ubuntu 14.04:
####Installing on Fedora: ####Installing on Fedora:
sudo yum install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel sudo dnf install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel patch rpm-build
git clone https://github.com/wpscanteam/wpscan.git git clone https://github.com/wpscanteam/wpscan.git
cd wpscan cd wpscan
sudo gem install bundler && bundle install --without test sudo gem install bundler && bundle install --without test
@@ -98,18 +149,20 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
cd wpscan cd wpscan
sudo gem install bundler && sudo bundle install --without test sudo gem install bundler && sudo bundle install --without test
####Installing with RVM: ####Installing with RVM (recommended):
# Install all prerequisites for your OS (look above)
cd ~ cd ~
curl -sSL https://get.rvm.io | bash -s stable curl -sSL https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm source ~/.rvm/scripts/rvm
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
rvm install 2.1.4 rvm install 2.3.1
rvm use 2.1.4 --default rvm use 2.3.1 --default
echo "gem: --no-ri --no-rdoc" > ~/.gemrc echo "gem: --no-ri --no-rdoc" > ~/.gemrc
gem install bundler gem install bundler
git clone https://github.com/wpscanteam/wpscan.git git clone https://github.com/wpscanteam/wpscan.git
cd wpscan cd wpscan
gem install bundler
bundle install --without test bundle install --without test
#### KNOWN ISSUES #### KNOWN ISSUES
@@ -139,7 +192,7 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
Then, open the directory of the readline gem (you have to locate it) Then, open the directory of the readline gem (you have to locate it)
cd ~/.rvm/src/ruby-1.9.2-p180/ext/readline cd ~/.rvm/src/ruby-XXXX/ext/readline
ruby extconf.rb ruby extconf.rb
make make
make install make install
@@ -157,13 +210,10 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
#### WPSCAN ARGUMENTS #### WPSCAN ARGUMENTS
--update Update the databases. --update Update the database to the latest version.
--url | -u <target url> The WordPress URL/domain to scan.
--url | -u <target url> The WordPress URL/domain to scan. --force | -f Forces WPScan to not check if the remote site is running WordPress.
--enumerate | -e [option(s)] Enumeration.
--force | -f Forces WPScan to not check if the remote site is running WordPress.
--enumerate | -e [option(s)] Enumeration.
option : option :
u usernames from id 1 to 10 u usernames from id 1 to 10
u[10-20] usernames from id 10 to 20 (you must write [] chars) u[10-20] usernames from id 10 to 20 (you must write [] chars)
@@ -177,53 +227,36 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
If no option is supplied, the default is "vt,tt,u,vp" If no option is supplied, the default is "vt,tt,u,vp"
--exclude-content-based "<regexp or string>" Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied --exclude-content-based "<regexp or string>"
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double) Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied.
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).
--config-file | -c <config file> Use the specified config file, see the example.conf.json --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.
--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.
--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.
--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not --no-color Do not use colors in the output.
--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it.
--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 Subdirectories are allowed.
--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory.
--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 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.
--proxy <[protocol://]host:port> Supply a proxy (will override the one from conf/browser.conf.json). If no protocol is given (format host:port), HTTP will be used.
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.
--proxy-auth <username:password> Supply the proxy login credentials. --wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.
--username | -U <username> Only brute force the supplied username.
--basic-auth <username:password> Set the HTTP Basic authentication. --usernames <path-to-file> Only brute force the usernames from the file.
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer. --cache-ttl <cache-ttl> Typhoeus cache TTL.
--request-timeout <request-timeout> Request Timeout.
--threads | -t <number of threads> The number of threads to use when multi-threading requests. --connect-timeout <connect-timeout> Connect Timeout.
--max-threads <max-threads> Maximum Threads.
--username | -U <username> Only brute force the supplied username. --throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.
--help | -h This help screen.
--usernames <path-to-file> Only brute force the usernames from the file. --verbose | -v Verbose output.
--version Output the current version and exit.
--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.
--log Save STDOUT to log.txt
#### WPSCAN EXAMPLES #### WPSCAN EXAMPLES
@@ -259,28 +292,13 @@ Debug output...
```ruby wpscan.rb --url www.example.com --debug-output 2>debug.log``` ```ruby wpscan.rb --url www.example.com --debug-output 2>debug.log```
#### WPSTOOLS ARGUMENTS
-v, --verbose Verbose output
--check-vuln-ref-urls, --cvru Check all the vulnerabilities reference urls for 404
--check-local-vulnerable-files, --clvf LOCAL_DIRECTORY Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells
-s, --stats Show WpScan Database statistics.
--spellcheck, --sc Check all files for common spelling mistakes.
#### WPSTOOLS EXAMPLES
Locally scan a wordpress installation for vulnerable files or shells:
```ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/```
#### PROJECT HOME #### PROJECT HOME
[http://www.wpscan.org](http://www.wpscan.org) [http://www.wpscan.org](http://www.wpscan.org)
#### VULNERABILITY DATABASE #### VULNERABILITY DATABASE
[https://www.wpvulndb.com](https://www.wpvulndb.com) [https://wpvulndb.com](https://wpvulndb.com)
#### GIT REPOSITORY #### GIT REPOSITORY

BIN
data.zip

Binary file not shown.

View File

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

19
dev/stats.rb Executable file
View File

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

View File

@@ -10,9 +10,9 @@
"cache_ttl": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled "cache_ttl": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
"request_timeout": 2000, // 2s "request_timeout": 60, // 1min
"connect_timeout": 1000, // 1s "connect_timeout": 10, // 10s
"max_threads": 20 "max_threads": 20
} }

View File

@@ -17,14 +17,15 @@ class Browser
:proxy_auth, :proxy_auth,
:request_timeout, :request_timeout,
:connect_timeout, :connect_timeout,
:cookie :cookie,
:throttle
] ]
@@instance = nil @@instance = nil
attr_reader :hydra, :cache_dir attr_reader :hydra, :cache_dir
attr_accessor :referer, :cookie attr_accessor :referer, :cookie, :vhost
# @param [ Hash ] options # @param [ Hash ] options
# #
@@ -70,14 +71,14 @@ class Browser
# sets browser default values # sets browser default values
# #
def browser_defaults def browser_defaults
@max_threads = 20 @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 # 10 minutes, at this time the cache is cleaned before each scan.
@cache_ttl = 600 # If this value is set to 0, the cache will be disabled
# 2s @cache_ttl = 600
@request_timeout = 2000 @request_timeout = 60 # 60s
# 1s @connect_timeout = 10 # 10s
@connect_timeout = 1000 @user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
@user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)" @throttle = 0
end end
# #
@@ -88,7 +89,6 @@ class Browser
# #
# @return [ void ] # @return [ void ]
def load_config(config_file = nil) def load_config(config_file = nil)
if File.symlink?(config_file) if File.symlink?(config_file)
raise '[ERROR] Config file is a symlink.' raise '[ERROR] Config file is a symlink.'
else else
@@ -101,7 +101,6 @@ class Browser
self.send(:"#{option_name}=", data[option_name]) self.send(:"#{option_name}=", data[option_name])
end end
end end
end end
# @param [ String ] url # @param [ String ] url
@@ -123,11 +122,8 @@ class Browser
) )
if @proxy if @proxy
params = params.merge(proxy: @proxy) params.merge!(proxy: @proxy)
params.merge!(proxyauth: @proxy_auth) if @proxy_auth
if @proxy_auth
params = params.merge(proxyauth: @proxy_auth)
end
end end
if @basic_auth if @basic_auth
@@ -138,19 +134,27 @@ class Browser
) )
end end
if vhost
params = Browser.append_params_header_field(
params,
'Host',
vhost
)
end
params.merge!(referer: referer) params.merge!(referer: referer)
params.merge!(timeout: @request_timeout) if @request_timeout params.merge!(timeout: @request_timeout) if @request_timeout && !params.key?(:timeout)
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout params.merge!(connecttimeout: @connect_timeout) if @connect_timeout && !params.key?(:connecttimeout)
# Used to enable the cache system if :cache_ttl > 0 # Used to enable the cache system if :cache_ttl > 0
params.merge!(cache_ttl: @cache_ttl) unless params.has_key?(:cache_ttl) params.merge!(cache_ttl: @cache_ttl) unless params.key?(:cache_ttl)
# Prevent infinite self redirection # Prevent infinite self redirection
params.merge!(maxredirs: 3) unless params.has_key?(:maxredirs) params.merge!(maxredirs: 3) unless params.key?(:maxredirs)
# Disable SSL-Certificate checks # Disable SSL-Certificate checks
params.merge!(ssl_verifypeer: false) params.merge!(ssl_verifypeer: false) unless params.key?(:ssl_verifypeer)
params.merge!(ssl_verifyhost: 0) params.merge!(ssl_verifyhost: 0) unless params.key?(:ssl_verifyhost)
params.merge!(cookiejar: @cache_dir + '/cookie-jar') params.merge!(cookiejar: @cache_dir + '/cookie-jar')
params.merge!(cookiefile: @cache_dir + '/cookie-jar') params.merge!(cookiefile: @cache_dir + '/cookie-jar')
@@ -174,5 +178,4 @@ class Browser
end end
params params
end end
end end

View File

@@ -3,8 +3,8 @@
class Browser class Browser
module Options module Options
attr_accessor :cache_ttl, :request_timeout, :connect_timeout attr_accessor :request_timeout, :connect_timeout
attr_reader :basic_auth, :proxy, :proxy_auth attr_reader :basic_auth, :cache_ttl, :proxy, :proxy_auth, :throttle
attr_writer :user_agent attr_writer :user_agent
# Sets the Basic Authentification credentials # Sets the Basic Authentification credentials
@@ -25,6 +25,10 @@ class Browser
end end
end end
def cache_ttl=(ttl)
@cache_ttl = ttl.to_i
end
# @return [ Integer ] # @return [ Integer ]
def max_threads def max_threads
@max_threads || 1 @max_threads || 1
@@ -82,7 +86,7 @@ class Browser
# #
# @return [ void ] # @return [ void ]
def request_timeout=(timeout) def request_timeout=(timeout)
@request_timeout = timeout @request_timeout = timeout.to_i
end end
# Sets the connect timeout # Sets the connect timeout
@@ -90,7 +94,12 @@ class Browser
# #
# @return [ void ] # @return [ void ]
def connect_timeout=(timeout) 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 end
protected protected
@@ -110,6 +119,5 @@ class Browser
end end
end end
end end
end end
end end

View File

@@ -23,9 +23,7 @@ class CacheFileStore
@storage_path = File.expand_path(File.join(storage_path, storage_dir)) @storage_path = File.expand_path(File.join(storage_path, storage_dir))
@serializer = serializer @serializer = serializer
# File.directory? for ruby <= 1.9 otherwise, unless Dir.exist?(@storage_path)
# it makes more sense to do Dir.exist? :/
unless File.directory?(@storage_path)
FileUtils.mkdir_p(@storage_path) FileUtils.mkdir_p(@storage_path)
end end
end end
@@ -51,7 +49,7 @@ class CacheFileStore
end end
def write_entry(key, data_to_store, cache_ttl) def write_entry(key, data_to_store, cache_ttl)
if cache_ttl > 0 if cache_ttl && cache_ttl > 0
File.open(get_entry_file_path(key), 'w') do |f| File.open(get_entry_file_path(key), 'w') do |f|
begin begin
f.write(@serializer.dump(data_to_store)) f.write(@serializer.dump(data_to_store))

View File

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

View File

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

View File

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

View File

@@ -2,17 +2,11 @@
class WpPlugins < WpItems class WpPlugins < WpItems
module Detectable module Detectable
# @return [ String ] # @return [ String ]
def vulns_file def vulns_file
PLUGINS_VULNS_FILE PLUGINS_FILE
end end
# @return [ String ]
# def item_xpath
# '//plugin'
# end
# @param [ WpTarget ] wp_target # @param [ WpTarget ] wp_target
# @param [ Hash ] options # @param [ Hash ] options
# #

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@ DATA_DIR = File.join(ROOT_DIR, 'data')
CONF_DIR = File.join(ROOT_DIR, 'conf') CONF_DIR = File.join(ROOT_DIR, 'conf')
CACHE_DIR = File.join(ROOT_DIR, 'cache') CACHE_DIR = File.join(ROOT_DIR, 'cache')
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan') WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan')
WPSTOOLS_LIB_DIR = File.join(LIB_DIR, 'wpstools')
UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater') UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater')
COMMON_LIB_DIR = File.join(LIB_DIR, 'common') COMMON_LIB_DIR = File.join(LIB_DIR, 'common')
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models') MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models')
@@ -17,24 +16,21 @@ LOG_FILE = File.join(ROOT_DIR, 'log.txt')
# Plugins directories # Plugins directories
COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins') COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins')
WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM
WPSTOOLS_PLUGINS_DIR = File.join(WPSTOOLS_LIB_DIR, 'plugins')
# Data files # Data files
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.txt') WORDPRESSES_FILE = File.join(DATA_DIR, 'wordpresses.json')
PLUGINS_FULL_FILE = File.join(DATA_DIR, 'plugins_full.txt') PLUGINS_FILE = File.join(DATA_DIR, 'plugins.json')
PLUGINS_VULNS_FILE = File.join(DATA_DIR, 'plugin_vulns.json') THEMES_FILE = File.join(DATA_DIR, 'themes.json')
THEMES_FILE = File.join(DATA_DIR, 'themes.txt') WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml')
THEMES_FULL_FILE = File.join(DATA_DIR, 'themes_full.txt') LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml')
THEMES_VULNS_FILE = File.join(DATA_DIR, 'theme_vulns.json') WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd')
WP_VULNS_FILE = File.join(DATA_DIR, 'wp_vulns.json') LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd')
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml') USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml') LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update')
# VULNS_XSD = File.join(DATA_DIR, 'vuln.xsd')
WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd')
LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd')
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
WPSCAN_VERSION = '2.6' MIN_RUBY_VERSION = '2.1.9'
WPSCAN_VERSION = '2.9.1'
$LOAD_PATH.unshift(LIB_DIR) $LOAD_PATH.unshift(LIB_DIR)
$LOAD_PATH.unshift(WPSCAN_LIB_DIR) $LOAD_PATH.unshift(WPSCAN_LIB_DIR)
@@ -42,22 +38,31 @@ $LOAD_PATH.unshift(MODELS_LIB_DIR)
def kali_linux? def kali_linux?
begin begin
File.readlines("/etc/debian_version").grep(/^kali/i).any? File.readlines('/etc/debian_version').grep(/^kali/i).any?
rescue rescue
false false
end end
end end
# Determins if installed on Windows OS
def windows?
Gem.win_platform?
end
require 'environment' require 'environment'
def escape_glob(s)
s.gsub(/[\\\{\}\[\]\*\?]/) { |x| '\\' + x }
end
# TODO : add an exclude pattern ? # TODO : add an exclude pattern ?
def require_files_from_directory(absolute_dir_path, files_pattern = '*.rb') def require_files_from_directory(absolute_dir_path, files_pattern = '*.rb')
files = Dir[File.join(absolute_dir_path, files_pattern)] files = Dir[File.join(escape_glob(absolute_dir_path), files_pattern)]
# Files in the root dir are loaded first, then those in the subdirectories # Files in the root dir are loaded first, then those in the subdirectories
files.sort_by { |file| [file.count("/"), file] }.each do |f| files.sort_by { |file| [file.count('/'), file] }.each do |f|
f = File.expand_path(f) f = File.expand_path(f)
#puts "require #{f}" # Used for debug # puts "require #{f}" # Used for debug
require f require f
end end
end end
@@ -80,6 +85,20 @@ def missing_db_file?
false false
end end
def last_update
date = nil
if File.exists?(LAST_UPDATE_FILE)
content = File.read(LAST_UPDATE_FILE)
date = Time.parse(content) rescue nil
end
date
end
def update_required?
date = last_update
(true if date.nil?) or (date < 5.days.ago)
end
# Define colors # Define colors
def colorize(text, color_code) def colorize(text, color_code)
if $COLORSWITCH if $COLORSWITCH
@@ -110,19 +129,21 @@ def blue(text)
end end
def critical(text) def critical(text)
red(text) $exit_code += 1 if defined?($exit_code) # hack for undefined var via rspec
"#{red('[!]')} #{text}"
end end
def warning(text) def warning(text)
amber(text) $exit_code += 1 if defined?($exit_code) # hack for undefined var via rspec
"#{amber('[!]')} #{text}"
end end
def info(text) def info(text)
green(text) "#{green('[+]')} #{text}"
end end
def notice(text) def notice(text)
blue(text) "#{blue('[i]')} #{text}"
end end
# our 1337 banner # our 1337 banner
@@ -209,7 +230,11 @@ end
# #
# @return [ Integer ] The number of lines in the given file # @return [ Integer ] The number of lines in the given file
def count_file_lines(file) def count_file_lines(file)
`wc -l #{file.shellescape}`.split[0].to_i if windows?
`findstr /R /N "^" #{file.shellescape} | find /C ":"`.split[0].to_i
else
`wc -l #{file.shellescape}`.split[0].to_i
end
end end
# Truncates a string to a specific length and adds ... at the end # Truncates a string to a specific length and adds ... at the end
@@ -243,3 +268,7 @@ end
def directory_listing_enabled?(url) def directory_listing_enabled?(url)
Browser.get(url.to_s).body[%r{<title>Index of}] ? true : false Browser.get(url.to_s).body[%r{<title>Index of}] ? true : false
end end
def url_encode(str)
CGI.escape(str).gsub("+", "%20")
end

View File

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

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

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

View File

@@ -1,35 +1,5 @@
# encoding: UTF-8 # encoding: UTF-8
# Since ruby 1.9.2, URI::escape is obsolete
# See http://rosettacode.org/wiki/URL_encoding#Ruby and http://www.ruby-forum.com/topic/207489
if RUBY_VERSION >= '1.9.2'
module URI
extend self
def escape(str)
URI::Parser.new.escape(str)
end
alias :encode :escape
end
end
if RUBY_VERSION < '1.9'
class Array
# Fix for grep with symbols in ruby <= 1.8.7
def _grep_(regexp)
matches = []
self.each do |value|
value = value.to_s
matches << value if value.match(regexp)
end
matches
end
alias_method :grep, :_grep_
end
end
# This is used in WpItem::Existable # This is used in WpItem::Existable
module Typhoeus module Typhoeus
class Response class Response
@@ -53,7 +23,7 @@ def puts(o = '')
temp = o.gsub(/\e\[\d+m/, '') # remove color for logging temp = o.gsub(/\e\[\d+m/, '') # remove color for logging
File.open(LOG_FILE, 'a+') { |f| f.puts(temp) } File.open(LOG_FILE, 'a+') { |f| f.puts(temp) }
end end
super(o) super(o)
end end
@@ -78,7 +48,7 @@ module Terminal
class Style class Style
@@defaults = { @@defaults = {
:border_x => "-", :border_y => "|", :border_i => "+", :border_x => '-', :border_y => '|', :border_i => '+',
:padding_left => 1, :padding_right => 1, :padding_left => 1, :padding_right => 1,
:margin_left => '', :margin_left => '',
:width => nil, :alignment => nil :width => nil, :alignment => nil
@@ -101,8 +71,21 @@ end
class Numeric class Numeric
def bytes_to_human def bytes_to_human
units = %w{B KB MB GB TB} units = %w{B KB MB GB TB}
e = (Math.log(self)/Math.log(1024)).floor e = (Math.log(abs)/Math.log(1024)).floor
s = "%.3f" % (to_f / 1024**e) s = '%.3f' % (abs.to_f / 1024**e)
s.sub(/\.?0*$/, ' ' + units[e]) s.sub(/\.?0*$/, ' ' + units[e])
end end
end end
# time calculations
class Fixnum
SECONDS_IN_DAY = 24 * 60 * 60
def days
self * SECONDS_IN_DAY
end
def ago
Time.now - self
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,29 +1,53 @@
# encoding: UTF-8 # encoding: UTF-8
class WpItem class WpItem
attr_writer :version attr_writer :version
module Versionable module Versionable
# Get the version from the readme.txt # Get the version from the readme.txt
# #
# @return [ String ] The version number # @return [ String ] The version number
def version def version
unless @version unless @version
# This check is needed because readme_url can return nil # This check is needed because readme_url can return nil
if has_readme? if has_readme?
response = Browser.get(readme_url) response = Browser.get(readme_url)
@version = response.body[%r{(?:stable tag|version): #{WpVersion.version_pattern}}i, 1] @version = extract_version(response.body)
end end
end end
@version @version
end end
# @return [ String ] # @return [ String ]
def to_s def to_s
item_version = self.version item_version = self.version
"#@name#{' - v' + item_version.strip if item_version}" "#{@name}#{' - v' + item_version.strip if item_version}"
end end
end # Extracts the version number from a given string/body
end #
# @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

View File

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

View File

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

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

View File

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

View File

@@ -14,7 +14,7 @@ class WpTheme < WpItem
def get_parent_theme_style_url def get_parent_theme_style_url
if is_child_theme? if is_child_theme?
return style_url.sub("/#{name}/style.css", "/#@theme_template/style.css") return style_url.sub("/#{name}/style.css", "/#{@theme_template}/style.css")
end end
nil nil
end end

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -3,8 +3,9 @@
require 'rubygems' require 'rubygems'
version = RUBY_VERSION.dup version = RUBY_VERSION.dup
if Gem::Version.create(version) < Gem::Version.create(1.9)
puts "Ruby >= 1.9 required to run wpscan (You have #{version})" if Gem::Version.create(version) < Gem::Version.create(MIN_RUBY_VERSION)
puts "Ruby >= #{MIN_RUBY_VERSION} required to run wpscan (You have #{version})"
exit(1) exit(1)
end end
@@ -29,12 +30,14 @@ begin
require 'shellwords' require 'shellwords'
require 'fileutils' require 'fileutils'
require 'pathname' require 'pathname'
require 'cgi'
# Third party libs # Third party libs
require 'typhoeus' require 'typhoeus'
require 'json' require 'yajl/json_gem'
require 'nokogiri' require 'nokogiri'
require 'terminal-table' require 'terminal-table'
require 'ruby-progressbar' require 'ruby-progressbar'
require 'addressable/uri'
# Custom libs # Custom libs
require 'common/browser' require 'common/browser'
require 'common/custom_option_parser' require 'common/custom_option_parser'

View File

@@ -54,10 +54,9 @@ class WebSite
redirected_uri = URI.parse(add_trailing_slash(add_http_protocol(url))) redirected_uri = URI.parse(add_trailing_slash(add_http_protocol(url)))
if response.code == 301 || response.code == 302 if response.code == 301 || response.code == 302
redirection = response.headers_hash['location'] redirection = redirected_uri.merge(response.headers_hash['location']).to_s
if redirection[0] == '/'
redirection = "#{redirected_uri.scheme}://#{redirected_uri.host}#{redirection}" return redirection if url == redirection # prevents infinite loop
end
# Let's check if there is a redirection in the redirection # Let's check if there is a redirection in the redirection
if other_redirection = redirection(redirection) if other_redirection = redirection(redirection)

View File

@@ -15,7 +15,6 @@ class WebSite
@uri.clone.merge('robots.txt').to_s @uri.clone.merge('robots.txt').to_s
end end
# Parse robots.txt # Parse robots.txt
# @return [ Array ] URLs generated from robots.txt # @return [ Array ] URLs generated from robots.txt
def parse_robots_txt def parse_robots_txt
@@ -29,6 +28,7 @@ class WebSite
if entries if entries
entries.flatten! entries.flatten!
entries.compact.sort! entries.compact.sort!
entries.uniq!
wordpress_path = @uri.path wordpress_path = @uri.path
RobotsTxt.known_dirs.each do |d| RobotsTxt.known_dirs.each do |d|
entries.delete(d) entries.delete(d)
@@ -40,9 +40,9 @@ class WebSite
entries.each do |d| entries.each do |d|
begin begin
temp = @uri.clone temp = @uri.clone
temp.path = d temp.path = d.strip
rescue URI::Error rescue URI::Error
temp = d temp = d.strip
end end
return_object << temp.to_s return_object << temp.to_s
end end

View File

@@ -21,14 +21,20 @@ class WpTarget < WebSite
attr_reader :verbose attr_reader :verbose
def initialize(target_url, options = {}) 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) super(target_url)
@verbose = options[:verbose] @verbose = options[:verbose]
@wp_content_dir = options[:wp_content_dir] @wp_content_dir = options[:wp_content_dir]
@wp_plugins_dir = options[:wp_plugins_dir] @wp_plugins_dir = options[:wp_plugins_dir]
@multisite = nil @multisite = nil
@vhost = options[:vhost]
Browser.instance.referer = url Browser.instance.referer = url
if @vhost
Browser.instance.vhost = @vhost
end
end end
# check if the target website is # check if the target website is
@@ -40,10 +46,12 @@ class WpTarget < WebSite
# Note: in the future major WPScan version, change the user-agent to see # Note: in the future major WPScan version, change the user-agent to see
# if the response is a 200 ? # 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" \ 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 '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 wordpress = true
else else
@@ -70,9 +78,7 @@ class WpTarget < WebSite
# Let's check if the login url is redirected (to https url for example) # Let's check if the login url is redirected (to https url for example)
redirection = redirection(url) redirection = redirection(url)
if redirection url = redirection if redirection
url = redirection
end
url url
end end
@@ -129,6 +135,11 @@ class WpTarget < WebSite
@uri.merge("#{wp_content_dir}/uploads/").to_s @uri.merge("#{wp_content_dir}/uploads/").to_s
end end
# @return [ String ]
def includes_dir_url
@uri.merge("wp-includes/").to_s
end
# Script for replacing strings in wordpress databases # Script for replacing strings in wordpress databases
# reveals database credentials after hitting submit # reveals database credentials after hitting submit
# http://interconnectit.com/124/search-and-replace-for-wordpress-databases/ # http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
@@ -147,4 +158,8 @@ class WpTarget < WebSite
def upload_directory_listing_enabled? def upload_directory_listing_enabled?
directory_listing_enabled?(upload_dir_url) directory_listing_enabled?(upload_dir_url)
end end
def include_directory_listing_enabled?
directory_listing_enabled?(includes_dir_url)
end
end end

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ class WpTarget < WebSite
@login_protection_plugin = nil @login_protection_plugin = nil
def has_login_protection? def has_login_protection?
!login_protection_plugin().nil? !login_protection_plugin.nil?
end end
# Checks if a login protection plugin is enabled # Checks if a login protection plugin is enabled
@@ -74,7 +74,7 @@ class WpTarget < WebSite
# http://wordpress.org/extend/plugins/login-security-solution/ # http://wordpress.org/extend/plugins/login-security-solution/
def has_login_security_solution_protection? def has_login_security_solution_protection?
Browser.get(login_security_solution_url()).code != 404 Browser.get(login_security_solution_url).code != 404
end end
def login_security_solution_url def login_security_solution_url
@@ -99,5 +99,12 @@ class WpTarget < WebSite
plugin_url('bluetrait-event-viewer') plugin_url('bluetrait-event-viewer')
end 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
end end

View File

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

View File

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

View File

@@ -62,7 +62,7 @@ def help
puts puts
puts 'Some values are settable in a config file, see the example.conf.json' puts 'Some values are settable in a config file, see the example.conf.json'
puts puts
puts '--update Update to the database to the latest version.' puts '--update Update the database to the latest version.'
puts '--url | -u <target url> The WordPress URL/domain to scan.' puts '--url | -u <target url> The WordPress URL/domain to scan.'
puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.' puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
puts '--enumerate | -e [option(s)] Enumeration.' puts '--enumerate | -e [option(s)] Enumeration.'
@@ -105,6 +105,7 @@ def help
puts '--request-timeout <request-timeout> Request Timeout.' puts '--request-timeout <request-timeout> Request Timeout.'
puts '--connect-timeout <connect-timeout> Connect Timeout.' puts '--connect-timeout <connect-timeout> Connect Timeout.'
puts '--max-threads <max-threads> Maximum Threads.' puts '--max-threads <max-threads> Maximum Threads.'
puts '--throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.'
puts '--help | -h This help screen.' puts '--help | -h This help screen.'
puts '--verbose | -v Verbose output.' puts '--verbose | -v Verbose output.'
puts '--version Output the current version and exit.' puts '--version Output the current version and exit.'
@@ -112,9 +113,20 @@ def help
end end
# Hook to check if the target if down during the scan # Hook to check if the target if down during the scan
# The target is considered down after 10 requests with status = 0 # And have the number of requests performed to display at the end of the scan
down = 0 # The target is considered down after 30 requests with status = 0
down = 0
@total_requests_done = 0
Typhoeus.on_complete do |response| Typhoeus.on_complete do |response|
next if response.cached?
down += 1 if response.code == 0 down += 1 if response.code == 0
fail 'The target seems to be down' if down >= 10 @total_requests_done += 1
fail 'The target seems to be down' if down >= 30
next unless Browser.instance.throttle > 0
sleep(Browser.instance.throttle)
end end

View File

@@ -1,7 +1,6 @@
# encoding: UTF-8 # encoding: UTF-8
class WpscanOptions class WpscanOptions
ACCESSOR_OPTIONS = [ ACCESSOR_OPTIONS = [
:batch, :batch,
:enumerate_plugins, :enumerate_plugins,
@@ -19,6 +18,7 @@ class WpscanOptions
:proxy_auth, :proxy_auth,
:threads, :threads,
:url, :url,
:vhost,
:wordlist, :wordlist,
:force, :force,
:update, :update,
@@ -41,7 +41,9 @@ class WpscanOptions
:cache_ttl, :cache_ttl,
:request_timeout, :request_timeout,
:connect_timeout, :connect_timeout,
:max_threads :max_threads,
:no_banner,
:throttle
] ]
attr_accessor *ACCESSOR_OPTIONS attr_accessor *ACCESSOR_OPTIONS
@@ -53,11 +55,17 @@ class WpscanOptions
end end
def url=(url) 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 @url = URI.parse(add_http_protocol(url)).to_s
end end
def vhost=(vhost)
@vhost = vhost
end
def threads=(threads) def threads=(threads)
@threads = threads.is_a?(Integer) ? threads : threads.to_i @threads = threads.is_a?(Integer) ? threads : threads.to_i
end end
@@ -243,6 +251,7 @@ class WpscanOptions
def self.get_opt_long def self.get_opt_long
GetoptLong.new( GetoptLong.new(
['--url', '-u', GetoptLong::REQUIRED_ARGUMENT], ['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
['--vhost',GetoptLong::OPTIONAL_ARGUMENT],
['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT], ['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT],
['--username', '-U', GetoptLong::REQUIRED_ARGUMENT], ['--username', '-U', GetoptLong::REQUIRED_ARGUMENT],
['--usernames', GetoptLong::REQUIRED_ARGUMENT], ['--usernames', GetoptLong::REQUIRED_ARGUMENT],
@@ -271,7 +280,9 @@ class WpscanOptions
['--batch', GetoptLong::NO_ARGUMENT], ['--batch', GetoptLong::NO_ARGUMENT],
['--no-color', GetoptLong::NO_ARGUMENT], ['--no-color', GetoptLong::NO_ARGUMENT],
['--cookie', GetoptLong::REQUIRED_ARGUMENT], ['--cookie', GetoptLong::REQUIRED_ARGUMENT],
['--log', GetoptLong::NO_ARGUMENT] ['--log', GetoptLong::NO_ARGUMENT],
['--no-banner', GetoptLong::NO_ARGUMENT],
['--throttle', GetoptLong::REQUIRED_ARGUMENT]
) )
end 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'].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 it 'raises an error' do
File.symlink('./testfile', config_file) File.symlink('./testfile', config_file)
expect { browser.load_config(config_file) }.to raise_error("[ERROR] Config file is a symlink.") expect { browser.load_config(config_file) }.to raise_error('[ERROR] Config file is a symlink.')
File.unlink(config_file) File.unlink(config_file)
end end
end end
@@ -130,7 +130,7 @@ describe Browser do
headers: { 'User-Agent' => 'SomeUA' }, headers: { 'User-Agent' => 'SomeUA' },
ssl_verifypeer: false, ssl_verifyhost: 0, ssl_verifypeer: false, ssl_verifyhost: 0,
cookiejar: cookie_jar, cookiefile: cookie_jar, cookiejar: cookie_jar, cookiefile: cookie_jar,
timeout: 2000, connecttimeout: 1000, timeout: 60, connecttimeout: 10,
maxredirs: 3, maxredirs: 3,
referer: nil referer: nil
} }
@@ -147,7 +147,6 @@ describe Browser do
@expected = default_expectation @expected = default_expectation
end end
context 'when @proxy' do context 'when @proxy' do
let(:proxy) { '127.0.0.1:9050' } let(:proxy) { '127.0.0.1:9050' }
let(:proxy_expectation) { default_expectation.merge(proxy: proxy) } let(:proxy_expectation) { default_expectation.merge(proxy: proxy) }
@@ -166,11 +165,19 @@ describe Browser do
end 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 context 'when @basic_auth' do
it 'appends the basic_auth' do it 'appends the basic_auth' do
browser.basic_auth = 'user:pass' browser.basic_auth = 'user:pass'
@expected = default_expectation.merge( @expected = default_expectation.merge(
headers: default_expectation[:headers].merge('Authorization' => 'Basic '+Base64.encode64('user:pass').chomp) headers: default_expectation[:headers].merge('Authorization' => 'Basic ' + Base64.encode64('user:pass').chomp)
) )
end end
end end

View File

@@ -92,7 +92,7 @@ describe CacheFileStore do
it 'should create a unique storage dir' do it 'should create a unique storage dir' do
storage_dirs = [] storage_dirs = []
(1..5).each do |i| (1..5).each do |_|
storage_dirs << CacheFileStore.new(cache_dir).storage_path storage_dirs << CacheFileStore.new(cache_dir).storage_path
end end

View File

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

View File

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

View File

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

View File

@@ -11,11 +11,11 @@ describe WpItem do
end end
it_behaves_like 'WpItem::Versionable' it_behaves_like 'WpItem::Versionable'
it_behaves_like 'WpItem::Vulnerable' do it_behaves_like 'WpItem::Vulnerable' do
let(:vulns_file) { MODELS_FIXTURES + '/wp_item/vulnerable/items_vulns.json' } let(:db_file) { MODELS_FIXTURES + '/wp_item/vulnerable/items_vulns.json' }
let(:identifier) { 'neo' } let(:identifier) { 'neo' }
let(:expected_refs) { { let(:expected_refs) { {
'id' => [2993], 'id' => [2993],
'url' => ['Ref 1,Ref 2'], 'url' => ['Ref 1', 'Ref 2'],
'cve' => ['2011-001'], 'cve' => ['2011-001'],
'secunia' => ['secunia'], 'secunia' => ['secunia'],
'osvdb' => ['osvdb'], 'osvdb' => ['osvdb'],
@@ -105,11 +105,6 @@ describe WpItem do
@expected = 'plugins/readme.txt' @expected = 'plugins/readme.txt'
end end
end end
it 'also encodes chars' do
@path = 'some dir with spaces'
@expected = 'some%20dir%20with%20spaces'
end
end end
describe '#uri' do describe '#uri' do

View File

@@ -5,11 +5,11 @@ require 'spec_helper'
describe WpPlugin do describe WpPlugin do
it_behaves_like 'WpPlugin::Vulnerable' it_behaves_like 'WpPlugin::Vulnerable'
it_behaves_like 'WpItem::Vulnerable' do it_behaves_like 'WpItem::Vulnerable' do
let(:options) { { name: 'white-rabbit' } } let(:options) { { name: 'white-rabbit' } }
let(:vulns_file) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins_vulns.json' } let(:db_file) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins.json' }
let(:expected_refs) { { let(:expected_refs) { {
'id' => [2993], 'id' => [2993],
'url' => ['Ref 1,Ref 2'], 'url' => ['Ref 1', 'Ref 2'],
'cve' => ['2011-001'], 'cve' => ['2011-001'],
'secunia' => ['secunia'], 'secunia' => ['secunia'],
'osvdb' => ['osvdb'], 'osvdb' => ['osvdb'],

View File

@@ -17,10 +17,9 @@ describe 'WpTheme::Findable' do
wp_theme = WpTheme.send(:find_from_css_link, uri) wp_theme = WpTheme.send(:find_from_css_link, uri)
if @expected expect(wp_theme).to be_a WpTheme if @expected
expect(wp_theme).to be_a WpTheme
end
expect(wp_theme).to eq @expected expect(wp_theme).to eq @expected
expect(wp_theme.wp_content_dir).to eql 'wp-content' if @expected
end end
context 'when theme is not present' do context 'when theme is not present' do
@@ -59,6 +58,13 @@ describe 'WpTheme::Findable' do
end end
end end
# This one might introduce FP btw
context 'when leaked from comments' do
it 'returns the WpTheme' do
@file = 'comments.html'
@expected = WpTheme.new(uri, name: 'debug')
end
end
end end
describe '::find_from_wooframework' do describe '::find_from_wooframework' do
@@ -96,7 +102,6 @@ describe 'WpTheme::Findable' do
@expected = WpTheme.new(uri, name: 'Editorial', version: '1.3.5') @expected = WpTheme.new(uri, name: 'Editorial', version: '1.3.5')
end end
end end
end end
describe '::find' do describe '::find' do
@@ -109,7 +114,6 @@ describe 'WpTheme::Findable' do
context 'when a method is named s_find_from_s' do context 'when a method is named s_find_from_s' do
it 'does not call it' do it 'does not call it' do
class WpTheme class WpTheme
module Findable module Findable
extend self extend self
@@ -117,7 +121,7 @@ describe 'WpTheme::Findable' do
end end
end end
stub_all_to_nil() stub_all_to_nil
expect { WpTheme.find(uri) }.to_not raise_error expect { WpTheme.find(uri) }.to_not raise_error
end end
@@ -125,7 +129,7 @@ describe 'WpTheme::Findable' do
context 'when the theme is not found' do context 'when the theme is not found' do
it 'returns nil' do it 'returns nil' do
stub_all_to_nil() stub_all_to_nil
expect(WpTheme.find(uri)).to be_nil expect(WpTheme.find(uri)).to be_nil
end end
@@ -133,7 +137,7 @@ describe 'WpTheme::Findable' do
context 'when the theme is found' do context 'when the theme is found' do
it 'returns it, with the :found_from set' do it 'returns it, with the :found_from set' do
stub_all_to_nil() stub_all_to_nil
stub_request(:get, /.+\/the-oracle\/style.css$/).to_return(status: 200) stub_request(:get, /.+\/the-oracle\/style.css$/).to_return(status: 200)
expected = WpTheme.new(uri, name: 'the-oracle') expected = WpTheme.new(uri, name: 'the-oracle')

View File

@@ -7,10 +7,10 @@ describe WpTheme do
it_behaves_like 'WpTheme::Vulnerable' it_behaves_like 'WpTheme::Vulnerable'
it_behaves_like 'WpItem::Vulnerable' do it_behaves_like 'WpItem::Vulnerable' do
let(:options) { { name: 'the-oracle' } } let(:options) { { name: 'the-oracle' } }
let(:vulns_file) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' } let(:db_file) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' }
let(:expected_refs) { { let(:expected_refs) { {
'id' => [2993], 'id' => [2993],
'url' => ['Ref 1,Ref 2'], 'url' => ['Ref 1', 'Ref 2'],
'cve' => ['2011-001'], 'cve' => ['2011-001'],
'secunia' => ['secunia'], 'secunia' => ['secunia'],
'osvdb' => ['osvdb'], 'osvdb' => ['osvdb'],

View File

@@ -65,6 +65,11 @@ describe 'WpVersion::Findable' do
@fixture = '/3.5_minified.html' @fixture = '/3.5_minified.html'
@expected = '3.5' @expected = '3.5'
end end
it 'returns 3.5.1' do
@fixture = '/3.5.1_mobile.html'
@expected = '3.5.1'
end
end end
end end
@@ -178,7 +183,7 @@ describe 'WpVersion::Findable' do
context 'when no version found' do context 'when no version found' do
it 'returns nil' do it 'returns nil' do
stub_all_to_nil() stub_all_to_nil
@expected = nil @expected = nil
end end
end end
@@ -188,8 +193,8 @@ describe 'WpVersion::Findable' do
found_from = method[/^find_from_(.*)/, 1].sub('_', ' ') found_from = method[/^find_from_(.*)/, 1].sub('_', ' ')
context "when found from #{found_from}" do context "when found from #{found_from}" do
it "returns the correct WpVersion" do it 'returns the correct WpVersion' do
stub_all_to_nil() stub_all_to_nil
allow(WpVersion).to receive(method).and_return(number) allow(WpVersion).to receive(method).and_return(number)

View File

@@ -4,20 +4,6 @@ require 'spec_helper'
describe WpVersion do describe WpVersion do
it_behaves_like 'WpVersion::Vulnerable' it_behaves_like 'WpVersion::Vulnerable'
it_behaves_like 'WpItem::Vulnerable' do
let(:options) { { number: '3.2' } }
let(:vulns_file) { MODELS_FIXTURES + '/wp_version/vulnerable/versions_vulns.json' }
let(:expected_refs) { {
'id' => [2993],
'url' => ['Ref 1,Ref 2'],
'cve' => ['2011-001'],
'secunia' => ['secunia'],
'osvdb' => ['osvdb'],
'metasploit' => ['exploit/ex1'],
'exploitdb' => ['exploitdb']
} }
let(:expected_vulns) { Vulnerabilities.new << Vulnerability.new('Here I Am', 'SQLI', expected_refs) }
end
subject(:wp_version) { WpVersion.new(uri, options) } subject(:wp_version) { WpVersion.new(uri, options) }
let(:uri) { URI.parse('http://example.com/') } let(:uri) { URI.parse('http://example.com/') }
@@ -29,4 +15,12 @@ describe WpVersion do
end end
end end
describe '#all' do
let(:versions_file) { File.join(MODELS_FIXTURES, 'wp_version', 'findable', 'advanced_fingerprinting', 'wp_versions.xml') }
it 'returns the array containign the two versions' do
expect(WpVersion.all(versions_file)).to eq ['3.2.1', '3.2']
end
end
end end

View File

@@ -121,4 +121,122 @@ describe 'VersionCompare' do
end end
end end
describe '::lesser?' do
context 'version checked is newer' do
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_truthy }
it 'returns true' do
@version1 = '1.0'
@version2 = '2.0'
end
it 'returns true' do
@version1 = '1.0'
@version2 = '1.1'
end
it 'returns true' do
@version1 = '1.0a'
@version2 = '1.0b'
end
it 'returns true' do
@version1 = '1.0'
@version2 = '5000000'
end
it 'returns true' do
@version1 = '0'
@version2 = '1'
end
it 'returns true' do
@version1 = '0.4.2b'
@version2 = '2.3.3'
end
it 'returns true' do
@version1 = '.47'
@version2 = '.50.3'
end
end
context 'version checked is older' do
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
it 'returns false' do
@version1 = '1'
@version2 = '0'
end
it 'returns false' do
@version1 = '1.0'
@version2 = '0.5'
end
it 'returns false' do
@version1 = '500000'
@version2 = '1'
end
it 'returns false' do
@version1 = '1.6.3.7.3.4'
@version2 = '1.2.4.567.679.8.e'
end
it 'returns false' do
@version1 = '.47'
@version2 = '.46.3'
end
end
context 'version checked is the same' do
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
it 'returns true' do
@version1 = '1'
@version2 = '1'
end
it 'returns true' do
@version1 = 'a'
@version2 = 'a'
end
end
context 'version number causes Gem::Version new Exception' do
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
it 'returns false' do
@version1 = 'a'
@version2 = 'b'
end
end
context 'one version number is not set' do
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
it 'returns false (version2 nil)' do
@version1 = '1'
@version2 = nil
end
it 'returns false (version1 nil)' do
@version1 = nil
@version2 = '1'
end
it 'returns false (version2 empty)' do
@version1 = '1'
@version2 = ''
end
it 'returns false (version1 empty)' do
@version1 = ''
@version2 = '1'
end
end
end
end end

View File

@@ -17,7 +17,7 @@ describe 'WebSite' do
) )
end end
describe "#new" do describe '#new' do
its(:url) { is_expected.to be === 'http://example.localhost/' } its(:url) { is_expected.to be === 'http://example.localhost/' }
end end
@@ -68,14 +68,14 @@ describe 'WebSite' do
describe '#xml_rpc_url' do describe '#xml_rpc_url' do
it 'returns the xmlrpc url' do it 'returns the xmlrpc url' do
expect(web_site.xml_rpc_url).to be === "http://example.localhost/xmlrpc.php" expect(web_site.xml_rpc_url).to be === 'http://example.localhost/xmlrpc.php'
end end
end end
describe '#has_xml_rpc?' do describe '#has_xml_rpc?' do
it 'returns true' do it 'returns true' do
stub_request(:get, web_site.xml_rpc_url). stub_request(:get, web_site.xml_rpc_url).
to_return(status: 200, body: "XML-RPC server accepts POST requests only") to_return(status: 200, body: 'XML-RPC server accepts POST requests only')
expect(web_site).to have_xml_rpc expect(web_site).to have_xml_rpc
end end
@@ -116,12 +116,24 @@ describe 'WebSite' do
expect(web_site.redirection).to eql absolute_location expect(web_site.redirection).to eql absolute_location
end end
context 'when starts with a ?' do
it 'returns the absolute URI' do
relative_location = '?p=blog'
absolute_location = web_site.uri.merge(relative_location).to_s
stub_request(:get, web_site.url).to_return(status: 301, headers: { location: relative_location })
stub_request(:get, absolute_location)
expect(web_site.redirection).to eql absolute_location
end
end
end end
context 'when multiple redirections' do context 'when multiple redirections' do
it 'returns the last redirection' do it 'returns the last redirection' do
first_redirection = 'www.redirection.com' first_redirection = 'http://www.redirection.com'
last_redirection = 'redirection.com' last_redirection = 'http://redirection.com'
stub_request(:get, web_site.url).to_return(status: 301, headers: { location: first_redirection }) stub_request(:get, web_site.url).to_return(status: 301, headers: { location: first_redirection })
stub_request(:get, first_redirection).to_return(status: 302, headers: { location: last_redirection }) stub_request(:get, first_redirection).to_return(status: 302, headers: { location: last_redirection })

View File

@@ -4,6 +4,7 @@ require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
describe WpTarget do describe WpTarget do
subject(:wp_target) { WpTarget.new(target_url, options) } subject(:wp_target) { WpTarget.new(target_url, options) }
subject(:wp_target_custom) { WpTarget.new(target_url, options_custom) }
let(:target_url) { 'http://example.localhost/' } let(:target_url) { 'http://example.localhost/' }
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR } let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR }
let(:login_url) { wp_target.uri.merge('wp-login.php').to_s } let(:login_url) { wp_target.uri.merge('wp-login.php').to_s }
@@ -15,6 +16,14 @@ describe WpTarget do
wp_plugins_dir: 'wp-content/plugins' wp_plugins_dir: 'wp-content/plugins'
} }
} }
let(:options_custom) {
{
config_file: SPEC_FIXTURES_CONF_DIR + '/browser.conf.json',
cache_ttl: 0,
wp_content_dir: 'custom-content',
wp_plugins_dir: 'custom-content/plugins'
}
}
before { Browser::reset } before { Browser::reset }
@@ -69,6 +78,11 @@ describe WpTarget do
expect(wp_target).to be_wordpress expect(wp_target).to be_wordpress
end end
it 'returns true if a custom content directory is detected' do
stub_request_to_fixture(url: wp_target_custom.url, fixture: fixtures_dir + '/wp_content_dir/wordpress-3.4.1-custom.htm')
expect(wp_target_custom).to be_wordpress
end
it 'returns true if the xmlrpc is found' do it 'returns true if the xmlrpc is found' do
stub_request(:get, wp_target.xml_rpc_url). stub_request(:get, wp_target.xml_rpc_url).
to_return(status: 200, body: File.new(fixtures_dir + '/xmlrpc.php')) to_return(status: 200, body: File.new(fixtures_dir + '/xmlrpc.php'))
@@ -135,7 +149,7 @@ describe WpTarget do
after :each do after :each do
allow(wp_target).to receive_messages(wp_content_dir: 'wp-content') allow(wp_target).to receive_messages(wp_content_dir: 'wp-content')
stub_request_to_fixture(url: wp_target.debug_log_url(), fixture: @fixture) stub_request_to_fixture(url: wp_target.debug_log_url, fixture: @fixture)
expect(wp_target.has_debug_log?).to be === @expected expect(wp_target.has_debug_log?).to be === @expected
end end

View File

@@ -32,6 +32,11 @@ describe 'WpscanOptions' do
@wpscan_options.url = url @wpscan_options.url = url
expect(@wpscan_options.url).to be === url expect(@wpscan_options.url).to be === url
end end
it 'should encode IDN' do
@wpscan_options.url = 'http://пример.испытание/'
expect(@wpscan_options.url).to be === 'http://xn--e1afmkfd.xn--80akhbyknj4f/'
end
end end
describe '#threads=' do describe '#threads=' do

View File

@@ -1,47 +0,0 @@
# encoding: UTF-8
require File.expand_path(File.dirname(__FILE__) + '/../../wpstools_helper')
describe 'StatsPlugin' do
subject(:stats) { StatsPlugin.new }
let(:plugins_vulns) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins_vulns.json' }
let(:themes_vulns) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' }
let(:plugins_file) { COLLECTIONS_FIXTURES + '/wp_plugins/detectable/targets.txt' }
let(:themes_file) { COLLECTIONS_FIXTURES + '/wp_themes/detectable/targets.txt'}
describe '#vuln_plugin_count' do
it 'returns the correct number' do
expect(stats.vuln_plugin_count(plugins_vulns)).to eq 2
end
end
describe '#vuln_theme_count' do
it 'returns the correct number' do
expect(stats.vuln_theme_count(themes_vulns)).to eq 2
end
end
describe '#plugin_vulns_count' do
it 'returns the correct number' do
expect(stats.plugin_vulns_count(plugins_vulns)).to eq 3
end
end
describe '#theme_vulns_count' do
it 'returns the correct number' do
expect(stats.theme_vulns_count(themes_vulns)).to eq 3
end
end
describe '#total_plugins' do
it 'returns the correct numer' do
expect(stats.total_plugins(plugins_file)).to eq 3
end
end
describe '#total_themes' do
it 'returns the correct numer' do
expect(stats.total_themes(themes_file)).to eq 3
end
end
end

View File

@@ -1,4 +0,0 @@
# encoding: UTF-8
require 'spec_helper'
require WPSTOOLS_LIB_DIR + '/wpstools_helper'

View File

@@ -1,58 +1,64 @@
[ {
{ "mr-smith": {
"mr-smith":{ "vulnerabilities":[
"vulnerabilities":[ {
{ "id":2989,
"id":2989, "title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1", "references": {
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com", "url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:43:41.000Z"
}, },
{ "created_at":"2014-07-28T12:10:07.000Z",
"id":2990, "updated_at":"2014-07-28T12:43:41.000Z"
"title":"Potential Authentication Cookie Forgery", },
"references":"https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be", {
"id":2990,
"title":"Potential Authentication Cookie Forgery",
"references": {
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
"osvdb":"105620", "osvdb":"105620",
"cve":"2014-0166", "cve":"2014-0166"
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2"
}, },
{ "created_at":"2014-07-28T12:10:07.000Z",
"id":2991, "updated_at":"2014-07-28T12:10:07.000Z",
"title":"Privilege escalation: contributors publishing posts", "fixed_in":"3.8.2"
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165", },
{
"id":2991,
"title":"Privilege escalation: contributors publishing posts",
"references": {
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
"osvdb":"105630", "osvdb":"105630",
"cve":"2014-0165", "cve":"2014-0165"
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2"
}, },
{ "created_at":"2014-07-28T12:10:07.000Z",
"id":2992, "updated_at":"2014-07-28T12:10:07.000Z",
"title":"Plupload Unspecified XSS", "fixed_in":"3.8.2"
},
{
"id":2992,
"title":"Plupload Unspecified XSS",
"references": {
"osvdb":"105622", "osvdb":"105622",
"secunia":"57769", "secunia":"57769"
"created_at":"2014-07-28T12:10:07.000Z", },
"updated_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "updated_at":"2014-07-28T12:10:07.000Z",
} "fixed_in":"3.8.2"
] }
} ]
}, },
{ "neo": {
"neo":{ "vulnerabilities":[
"vulnerabilities":[ {
{ "id":2993,
"id":2993, "title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure", "references": {
"references":"http://seclists.org/fulldisclosure/2013/Dec/135", "url": "http://seclists.org/fulldisclosure/2013/Dec/135",
"osvdb":"101101", "osvdb":"101101"
"created_at":"2014-07-28T12:10:07.000Z", },
"updated_at":"2014-07-28T12:10:07.000Z" "created_at":"2014-07-28T12:10:07.000Z",
} "updated_at":"2014-07-28T12:10:07.000Z"
] }
} ]
} }
] }

View File

@@ -1,58 +1,64 @@
[ {
{ "mr-smith": {
"mr-smith":{ "vulnerabilities":[
"vulnerabilities":[ {
{ "id":2989,
"id":2989, "title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1", "references": {
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com", "url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:43:41.000Z"
}, },
{ "created_at":"2014-07-28T12:10:07.000Z",
"id":2990, "updated_at":"2014-07-28T12:43:41.000Z"
"title":"Potential Authentication Cookie Forgery", },
"references":"https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be", {
"osvdb":"105620", "id":2990,
"cve":"2014-0166", "title":"Potential Authentication Cookie Forgery",
"created_at":"2014-07-28T12:10:07.000Z", "references": {
"updated_at":"2014-07-28T12:10:07.000Z", "url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be"
"fixed_in":"3.8.2"
}, },
{ "osvdb":"105620",
"id":2991, "cve":"2014-0166",
"title":"Privilege escalation: contributors publishing posts", "created_at":"2014-07-28T12:10:07.000Z",
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165", "updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2"
},
{
"id":2991,
"title":"Privilege escalation: contributors publishing posts",
"references": {
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
"osvdb":"105630", "osvdb":"105630",
"cve":"2014-0165", "cve":"2014-0165"
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2"
}, },
{ "created_at":"2014-07-28T12:10:07.000Z",
"id":2992, "updated_at":"2014-07-28T12:10:07.000Z",
"title":"Plupload Unspecified XSS", "fixed_in":"3.8.2"
},
{
"id":2992,
"title":"Plupload Unspecified XSS",
"references": {
"osvdb":"105622", "osvdb":"105622",
"secunia":"57769", "secunia":"57769"
"created_at":"2014-07-28T12:10:07.000Z", },
"updated_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "updated_at":"2014-07-28T12:10:07.000Z",
} "fixed_in":"3.8.2"
] }
} ]
}, },
{ "neo": {
"neo":{ "vulnerabilities":[
"vulnerabilities":[ {
{ "id":2993,
"id":2993, "title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure", "references": {
"references":"http://seclists.org/fulldisclosure/2013/Dec/135", "url": "http://seclists.org/fulldisclosure/2013/Dec/135",
"osvdb":"101101", "osvdb":"101101"
"created_at":"2014-07-28T12:10:07.000Z", },
"updated_at":"2014-07-28T12:10:07.000Z" "created_at":"2014-07-28T12:10:07.000Z",
} "updated_at":"2014-07-28T12:10:07.000Z"
] }
} ]
} }
] }

View File

@@ -1,58 +1,65 @@
[ {
{ "shopperpress": {
"shopperpress":{ "vulnerabilities":[
"vulnerabilities":[ {
{ "id":2989,
"id":2989, "title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1", "references": {
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com", "url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
"created_at":"2014-07-28T12:10:07.000Z",
"updated_at":"2014-07-28T12:43:41.000Z"
}, },
{ "created_at":"2014-07-28T12:10:07.000Z",
"id":2990, "updated_at":"2014-07-28T12:43:41.000Z"
"title":"Potential Authentication Cookie Forgery", },
"references":"https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be", {
"osvdb":"105620", "id":2990,
"cve":"2014-0166", "title":"Potential Authentication Cookie Forgery",
"created_at":"2014-07-28T12:10:07.000Z", "references": {
"updated_at":"2014-07-28T12:10:07.000Z", "url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
"fixed_in":"3.8.2" "osvdb":"105620",
"cve":"2014-0166"
}, },
{ "created_at":"2014-07-28T12:10:07.000Z",
"id":2991, "updated_at":"2014-07-28T12:10:07.000Z",
"title":"Privilege escalation: contributors publishing posts", "fixed_in":"3.8.2"
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165", },
"osvdb":"105630", {
"cve":"2014-0165", "id":2991,
"created_at":"2014-07-28T12:10:07.000Z", "title":"Privilege escalation: contributors publishing posts",
"updated_at":"2014-07-28T12:10:07.000Z", "references": {
"fixed_in":"3.8.2" "url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
"osvdb":"105630",
"cve":"2014-0165"
}, },
{ "created_at":"2014-07-28T12:10:07.000Z",
"id":2992, "updated_at":"2014-07-28T12:10:07.000Z",
"title":"Plupload Unspecified XSS", "fixed_in":"3.8.2"
},
{
"id":2992,
"title":"Plupload Unspecified XSS",
"references": {
"osvdb":"105622", "osvdb":"105622",
"secunia":"57769", "secunia":"57769"
"created_at":"2014-07-28T12:10:07.000Z", },
"updated_at":"2014-07-28T12:10:07.000Z", "created_at":"2014-07-28T12:10:07.000Z",
"fixed_in":"3.8.2" "updated_at":"2014-07-28T12:10:07.000Z",
} "fixed_in":"3.8.2"
] }
} ]
}, },
{ "webfolio": {
"webfolio":{ "vulnerabilities":[
"vulnerabilities":[ {
{ "id":2993,
"id":2993, "title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure", "references": {
"references":"http://seclists.org/fulldisclosure/2013/Dec/135", "url": "http://seclists.org/fulldisclosure/2013/Dec/135",
"osvdb":"101101", "osvdb":"101101"
"created_at":"2014-07-28T12:10:07.000Z", },
"updated_at":"2014-07-28T12:10:07.000Z" "created_at":"2014-07-28T12:10:07.000Z",
} "updated_at":"2014-07-28T12:10:07.000Z"
] }
} ]
} }
] }

View File

@@ -1,12 +1,14 @@
{ {
"id": "3911", "id": "3911",
"title": "Vuln Title", "title": "Vuln Title",
"url": "Ref 1,Ref 2", "references":{
"secunia": "secunia", "url": "Ref 1,Ref 2",
"osvdb": "osvdb", "secunia": "secunia",
"cve": "2011-001", "osvdb": "osvdb",
"metasploit": "exploit/ex1", "cve": "2011-001",
"exploitdb": "exploitdb", "metasploit": "exploit/ex1",
"exploitdb": "exploitdb"
},
"created_at": "2014-07-28T12:10:45.000Z", "created_at": "2014-07-28T12:10:45.000Z",
"updated_at": "2014-07-28T12:10:45.000Z", "updated_at": "2014-07-28T12:10:45.000Z",
"type": "CSRF", "type": "CSRF",

View File

@@ -0,0 +1,103 @@
=== A Capture Contact Form (and tab) by AWebVoice.com ===
Contributors: AWebVoice
Donate link:
Tags: contact form, web form, capture contact form, response forms, captcha form, get response, contact me, contact form 7
Requires at least: 2.0.2
Tested up to: 3.1.1
Stable tag: trunk
Get a contact form and a contact button. Capture your visitors and turn them into customers!
== Description ==
A fully customizable contact form on your WordPress blog. And a contact tab to increase customer interaction. Join our fast growing users base who have chosen AWebVoice as their contact form of choice!
= Get your FREE Contact Form plugin for Wordpress =
Our Contact Form plugin is full of features that no other wordpress contact form can offer. See for yourself, below are a few of the features we offer:
* Add a customized contact form to your Wordpress blog which includes a message, contact's email, name, phone number and more!
* Create and customize your contact form settings right from within your Wordpress Admin Panel
* Include your logo, business contact info, even social links right on your contact form
* Notifications: Get a contacts message notifications to your inbox!
* Setup multiple autoresponder for your contact form so your visitors get an instant message from you
* Take your autoresponders to the next level, and ask your contact to join your email list.
* Setup a custom success message or URL for your contact form
* Each form has built in ROI tracking
* Increases conversion: Include a custom “Contact” tab to the side of your wordpress blog that pops up your contact form.
* ...these features will always be free, but go to the next level and get many more features for your contact form!
= More than a Contact Form =
Behind the AWebVoice wordpress plugin contact form is a suite of online tools to effectively manage all of your leads, contacts, and marketing ROI needs. As leads come in from your contact form, those contacts are automatically added to your online contacts database. From there, access information about each contact, send emails to each contact, and fully track, organize, and manage your communications. AWebVoice.com is designed to scale from single person offices to Fortune 500 companies -- and it is created on the very largest online database has to offer.
= And it is easy to get start =
AWebVoice.com’s contact form is FREE and is the easiest wordpress contact form to use. We think you will agree, give it a try sign up now:
www.awebvoice.com.
== Installation ==
= Option 1: Install the plugin via your Wordpress admin panel =
1. Login to your Wordpress system which should take you to the Dashboard of your Wordpress account.
1. Click the "Plugins" menu on the left menu bar. The choose "Add New".
1. Search for "AWebVoice", "Contact " or "contact form".
1. Click Install Now, the "Contact Form" by AWebVoice.com.
1. After installation has finished, you need to activate the plugin.
1. You should see the AWebVoice plugin listed in the available plugins. Click the "Activate" link.
1. Next, click the "Settings" menu on the left menu bar and choose "AWebVoice Form"
1. A form will be presented. Enter the e-mail address to receive contact form messages, and click "Create Account".
1. Your AWebVoice tab-button and contact form have been installed and are working on your blog.
= Option 2: Manual plugin installation =
1. Click on the red "Download Version x.x" button on the right side of this page.
1. After the download has finished, extract the files.
1. Upload the "awebvoivce" folder to your server in "/wp-content/plugins" directory
1. Login to you Wordpress system which should take you to the admin panel or Dashboard. Click on "Plugins" menu.
1. You should see the AWebVoice plugin listed in the available plugins. Click the "Activate" link.
1. Then click on the "Settings" menu in the left side menu bar. Choose "AWebVoice Form"
1. A form will be presented. Enter the e-mail address to receive contact form messages, and click "Create Account".
1. Your AWebVoice tab-button and contact form have been installed and are working on your blog.
== Frequently Asked Questions ==
= What is Contact Form by AWebVoice.com? =
Unlike other contact form providers like contact form 7, you will never have to touch a line of code to create, edit, or modify your contact form. Just install the Awebvoice.com contact form plugin, choose the email address you want your form submissions to go to, and that is it. Your new contacts are delivered to the specified email address AND stored in an online database under your name. Your contacts are yours and not shared with others. How you use your new contacts is up to you.
= Can I customize my contact form? =
Yes. Editing a form is quick and easy. In your WordPress administration page, click on Settings. Click on the AWebVoice plugin and click the modify button to edit your contact form. (You may have to login first to AWebVoice.com) With the AWebVoice contact form editor, you can create a beautiful and customized contact forms, including your logo, your address and phone, and more.
= What is the AWebVoice tab-button? =
The tab-button is a button that sits on the side of your blog and maintains position even as your visitors scroll. Your potential new contacts or existing contacts are only one click away from sending you a message! When the button is clicked, your contact form pops right up in a nice modal window, darkening the rest of the screen and focusing the user on completing your contact form. This AWebVoice tab-button and contact form combination has been proven improve conversions on a website by over 45%.
= What other unique features do you offer? =
Many more features. AWebVoice is a full featured email, newsletter, coupon marketing system. The tab-button and contact form will always be free. But if you need more, such as self-managed email lists, we have it. We also have ROI reports so you can track your contact form and blog success rate. The AWebVoice Email/Newsletter management system is a subscription based system. We want to help you get started, and once the task of managing your contact list get too large, we are there to support your efforts. AWebVoice is the most effective marketing tool a small or large business can have.
Get your free AWebVoice contact form today and start watching your leads grow!
== Screenshots ==
1. Contact Form Admin Panel
2. A Contact Form
3. Contact Tab-Button on the Blog (click opens contact form)
== Change Log ==
= Coming Soon =
* Contact Form: More custom from fields for your contact form
* Contact Form: More languages for button and contact form
= 3.1 =
* Initial WordPress Release.
* Analytics included in Contact Form
* Custom form fields for your contact form
* Form title is editable
== Upgrade Notice ==
= 1.0 =
Upgrade notices when available will be described in this section.
= Languages =
* English: Available contact form and button

View File

@@ -0,0 +1,13 @@
=== Plugin Name ===
AA health calculator
Tags:aa ,health,calculator
Requires at least:4.0
Tested up to:4.0
Stable tag:aahealthcalculator
License: GPL
Contributors :A and A
==Description==
use [health] shortcode

View File

@@ -0,0 +1,215 @@
=== Advanced Most Recent Posts Mod ===
Contributors: yakuphan, trepmal
Tags: Advanced, recent, recent posts, most recent, category posts, thumbnail
Donate link: http://kaileylampert.com/donate/
Requires at least: 2.8
Tested up to: 3.2.1
Stable tag: trunk
Based off the Advanced Most Recent Posts plugin by Yakup GÃVLER. Display most recent posts from selected categories or current category or all categories with thumbnail images (optional).
== Description ==
Advanced Most Recent Posts Widget displays your recent posts with thumbnail images (optional). It gets posts from selected categories or current category or all categories. When your visitors are at home, it gets posts from all posts or selected category. If you set 'Get posts from current category', when visitors see single post, widget lists posts in the same category of single post or when visitors click a category link, it gets posts from current category.
Check out a brand new plugin inspired by this one: [Mini Loops](http://wordpress.org/extend/plugins/mini-loops/)
I'm bad at support (not intentionally). If you have questions: check the forums, then check [my blog post's comment thread](http://trepmal.com/plugins/advanced-most-recent-posts-mod/#comments). If that doesn't work, post to the forums (there are awesome volunteers out there!) and get my attention by pinging me on twitter [@trepmal](http://twitter.com/trepmal).
== Installation ==
= Installation =
1. Make sure you are running WordPress version 2.8 or better. It won't work with older versions.
2. Download the zip file and extract the contents.
3. Upload the 'advanced-most-recent-posts' folder (wp-content/plugins/).
4. Activate the plugin through the 'plugins' page in WP.
5. See 'Appearance'->'Widgets' to place it on your sidebar. Set the settings.
== Frequently Asked Questions ==
= How can I set it to get posts from current category? =
Select checkbox on widget's settings called 'Get posts from current category'.
= I want to display only the posts in two categories. =
You have to write their category's ids -separated with a comma- to 'Categories' textbox.
= I don't use Widgets. How can use this widget? =
template tag: `yg_recentposts( $args )`
shortcode: `[amrp]` with args
Original author's [website](http://www.yakupgovler.com/?p=1033).
= .... Image alignment .... =
If you need serious customization, please go learn CSS. I will not provide extensive CSS support. (Sorry - there are just far too many variations, and I do all this in my spare time)
The most frequest request I get has to do with aligning the image to the right or left. To get started with customization, create a file called amrp-styles.php (really, any name will do) and upload it to `wp-content/mu-plugins` (you may need to create this directory).
In that file, paste the following
`<?php
/*
AMRP Styles
*/
add_action('wp_head', 'amrp_styles');
function amrp_styles() {
?>
<style type="text/css">
.advanced-recent-posts {
/* remove bullet points */
list-style-type: none;
}
.advanced-recent-posts li {
}
.advanced-recent-posts li a {
}
.advanced-recent-posts li img {
float: right; /* change to left if you'd prefer */
margin: 3px;
}
</style>
<?php
}`
Of course, if you have the skills, feel free to relocate that CSS.
== Screenshots ==
1. Widget's screenshot in 'Appearance'->'Widgets'
2. (original version) Widget's screenshot in 'Appearance'->'Widgets'
== Options ==
Widget's options allow you to change your recent posts list displaying.
= Title: =
Your recent posts widget's title on your sidebar.
= Title Link: =
The page the title should link to.
= Hide Post Title: =
Check to hide post title in output. useful for thumbnail-only displays
= Separator: =
The character to use to separate the title from the excerpt.
= After Excerpt: =
What should appear after the excerpt
= After Excerpt Link: =
should the 'after excerpt' text link to the post? useful if 'after excerpt' read like "read more..."
= Show: =
The post type to be displayed.
= Number of posts to show: =
How many posts to display
= Excerpt length (letters) =
You know that
= Thumbnail Custom Field Name =
If you want to display the thumbnail of your posts via a custom field, write its name.
= Height - Width =
Images size.
= Get first image of post =
If you don't want to use custom field, plugin will get first image from your post content.
= Get first attached image of post =
Plugin gets first attached image of post.
= Default image =
If post has no image, plugin display this image. Ex: http://www.yakupgovler.com/default-image.png
Notice: If you use three options, plugin uses custom field image firstly. If the post has no custom field, it gets first image from content. At last it gets first attached image. I suggest not to use "Get first image of post" for performance. It queries much more.
= Show Author =
If checked, shows author next to title
= Show Post Timestamp =
If checked, shows post timestamp
= Time format =
The format to be used when displaying the timestamp
= Put time =
A placement option for the post timestamp
= Categories =
Plugin gets posts in these categories. (Category IDs, separated by commas.)
= Get posts from current category: =
Posts will be get from current category (single post's category or current category).
== Upgrade Notice ==
= 1.6.5 =
bugfix: 'after excerpt' now appears as it should even if the link option is unchecked
Have you tried [Mini Loops](http://wordpress.org/extend/plugins/mini-loops/)?
= 1.6.4 =
moved image outside of title so it won't disappear if 'hide title' is checked
undefined variable bug fix
= 1.6.3 =
fixed after-excerpt-link bug
= 1.6 =
Future updates will assume you have at least this release.
== Changelog ==
= 1.6.5.2 =
* it title is empty, show nothing not default
= 1.6.5.1 =
* updated POT file
= 1.6.5 =
* bugfix: 'after excerpt' now appears as it should even if the link option is unchecked
= 1.6.4 =
* moved image outside of title so it won't disappear if 'hide title' is checked
* undefined variable bug fix
= 1.6.3 =
* fixed after-excerpt-link bug
= 1.6.2 =
* shortcode fixes for 'limit' (really should be 'shownum' - now both work) and height/width
= 1.6.1 =
* author fix
= 1.6 =
* remove 20 post limit
* option to display posts in reverse order
* excerpt by *word* count option
* post-offset option
= 1.5 =
* *dev release*
* option to hide post title
* option to specify the '...' after excerpt
= 1.4.1 =
* fixed double echo issue
= 1.4 =
* added support for shortcodes, show author option and post-type choice
= 1.3 =
* fixed timestamp bug, added timestamp placement option
= 1.2 =
* added support for setting a title link, choosing a title/content separator, and displaying post timestamp
= 1.1 =
* (original plugin) Fixed a bug. If you don't set image dimensions, it displays thumbnail wrong.
= Version 1.0 =
* Initial release version.

View File

@@ -0,0 +1,39 @@
=== All In One Facebook ===
Contributors: rahadgp
Donate link:
Tags: facebook,Social Networking,Wordpress Facebook widget, twiter widget
Requires at least: 3.3
Tested up to: 3.8
Stable tag: all in one facebook
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
With this widget and plugin combination , you can display a customizable JQUERY accordion which gathers Facebook social plugins together with option which to be display or which you don't want to display in your side bar or widget area, and by the plugin you can set short code for each and every thing what you want to put in your post on your website .
== Description ==
With this widget , you can display a customizable frame which gathers Facebook social plugins together with option which to be display or which you don't want to display in your side bar or widget area, and by the plugin you can set short code for each and every thing what you want to put in your post on your website . As a whole new feature like and share feature has been implemented with like box, recommends and twitter feed integration.
== Installation ==
1. Upload `plugin-name.php` to the `/wp-content/plugins/` directory
1. Activate the plugin through the 'Plugins' menu in WordPress
== Frequently asked questions ==
== Screenshots ==
1. Setting Page for the facebook plugin
2. Setting page
3. Setting Page
== Changelog ==
== Upgrade notice ==
== Arbitrary section 1 ==

View File

@@ -0,0 +1,224 @@
=== Backup Scheduler ===
Author: SedLex
Contributors: SedLex
Author URI: http://www.sedlex.fr/
Plugin URI: http://wordpress.org/plugins/backup-scheduler/
Tags: backup, schedule, plugin, save, database, zip
Requires at least: 3.0
Tested up to: 4.2
Stable tag: trunk
License: GPLv3
With this plugin, you may plan the backup of your entire website (folders, files and/or database).
== Description ==
With this plugin, you may plan the backup of your entire website (folders, files and/or database).
You can choose:
* which folders you want to save;
* the frequency of the backup process;
* whether your database should be saved;
* whether the backup is stored on the local website, sent by email or stored on a distant FTP (support of multipart zip files)
This plugin is under GPL licence
= Multisite - Wordpress MU =
This plugin is compatible with Multisite installation.
Each blog administrator may save their own data.
The super-admin may save either its data or the whole website. By saving the whole site, the admin may create different SQL files for the subsite in order to ease the restoration of a single sub-site.
= Localization =
* German (Switzerland) translation provided by PeterDbbert, BernhardKnab, scream
* German (Germany) translation provided by agent-test, agent, bartdev2000, Ditoran, GLassnig
* English (United States), default language
* Spanish (Spain) translation provided by Javier, AVfoto, charliechin, IgnacioCalvo, JordiVives, FelipeJAG
* Farsi (Iran) translation provided by sehrama.ir
* Finnish (Finland) translation provided by AnttiSilvola
* French (France) translation provided by SedLex, wkpixearts, Matthieu, mutmut, anonymous, noaneo, TonyLand
* Indonesian (Indonesia) translation provided by ceceparif
* Indonesian (Indonesia) translation provided by Faleddo
* Italian (Italy) translation provided by PuntoCon
* Dutch (Netherlands) translation provided by Matrix, WybAnema, Jay
* Polish (Poland) translation provided by Opti, Lukasz, pablo, Misiek, MarekMackiewicz, Darbo
* Portuguese (Brazil) translation provided by RainilsonRodriguis, GuiBeloto
* Portuguese (Portugal) translation provided by FranciscoRocha
* Russian (Russia) translation provided by GerinG, Slawka, Berdych
* Swedish (Sweden) translation provided by
* Thai (Thailand) translation provided by tontan
* Turkish (Turkey) translation provided by UfukArt
* Chinese (People's Republic of China) translation provided by YiscaJoe, jeffli
= Features of the framework =
This plugin uses the SL framework. This framework eases the creation of new plugins by providing tools and frames (see dev-toolbox plugin for more info).
You may easily translate the text of the plugin and submit it to the developer, send a feedback, or choose the location of the plugin in the admin panel.
Have fun !
== Installation ==
1. Upload this folder backup-scheduler to your plugin directory (for instance '/wp-content/plugins/')
2. Activate the plugin through the 'Plugins' menu in WordPress
3. Navigate to the 'SL plugins' box
4. All plugins developed with the SL core will be listed in this box
5. Enjoy !
== Screenshots ==
1. A list of all backup files
2. The configuration page of the plugin
== Changelog ==
= 1.5.9 =
* NEW: Add icons
= 1.5.8 =
* NEW: Exclusion folder is now possible with regexp
= 1.5.7 =
* NEW: Exclusion folder enabled
* NEW: Detailed HOW TO
= 1.5.6 =
* BUG : Problem of activation with version of PHP below 5.2
= 1.5.5 =
* NEW : Take into account blogs.dir and site
= 1.5.4 =
* NEW : By saving the whole site, the admin may create different SQL files for the subsite in order to ease the restoration of a single sub-site.
= 1.5.3 =
* BUG: On some configuration, &lt;? is not supported
= 1.5.2 =
* NEW: You may now create subfolder in the FTP directory
* NEW: improve the look of the configuration page
= 1.5.1 =
* BUG: improve the summary mail
* NEW: indicate if the FTP transfer has been successful in the backend
* NEW: few enhancement in the framework
= 1.5.0 =
* Major improvement of the database backup
* the summary mail now displays the issues with the ftp transfer
= 1.4.0 -&gt; 1.4.4 =
* Change the URL of the plugin on Wordpress
* Some modification
* Some issues in the framework
* Cleaning the framework to avoid unnecessarly code
* A bug that do not delete the lock file when reseting the backup process
* Enhance the performance of the backup process and ensure error protection
* Improve the mail summary
* Enhance the feedback tab
* Improve the core
= 1.3.0 -&gt; 1.3.7 =
* FTP bug with some webhosting service
* FTP port may be changed
* The error message is muck more explicit
* Add a drop if exist in SQL table
* Bug with multisite and remove a false positive error with wordfence
* There was a bug in the regexp when the ftp were directed to the root folder without any slash at the end.
* Add deletion features when uninstalling the plugin
* Multisite compatible
* Improve the zip compatibilities
* Add log features
= 1.2.0 -&gt; 1.2.8 =
* Some spanned zip files were corrupted due to a bug in the index
* Remove short_open_tag
* Tuning to be able to work with very huge database
* Bug with NULL values in the database
* FTP support
* Full site backup is now possible
* Bug correction when SQL has NULL value
* Add a link to delete manually the backup (feature requested by Mirza)
* You can also force a new update without sending the emails
* Improve error management and memory leakage
= 1.1.0 -&gt; 1.1.5 =
* Bug in the sql file : date and time managements were incorrect
* Add a time option for choosing the best moment to perform an automatic backup
* Display bug correction
* Add instructions to restore the backup :)
* Improve memory and time management for database extraction
* Add error messages if it is impossible to read/delete/modify files
* Add time and memory management for constrained configuration
* Improving zip decompression and path
* Correction of a bug that occurs when server refuse to access / directory "open_basedir" restriction
* Update of the core
= 1.0.1 =
* First release in the wild web (enjoy)
== Frequently Asked Questions ==
= Forced backup never ends (but there is no displayed error) =
Be sure to stay on the configuration page : if you quit the page, the forced backup process will be killed !
= Scheduled backup is stucked =
Scheduled backup only works on website that have traffic.
Indeed, each visits triggers a piece of the backup process.
Thus, if there is no traffic, the schedule backup process wont't occur. If there is very little traffic, the backup will be very long, etc
= I have an error message indicating that another backup is running =
This message may happen if the chunk size is set quite high. For instance, 40 Mo is clearly too big and server server configuration of many webhosters will kill scripts which use too much memory.
Most of the case 5Mo is ok.
If you get this error, set the chunk size to 1Mo and if it solves your problem, increase this chunk size.
= Compatible Archive Software =
The backup will be in a multi-part format. In order to uncompress it, you should put all the backup in the same folder and open the .zip file with Winzip.
You may experience some "corruption" error. It is mainly due that archive software are not compatible with multi-part archives. I have tried with success:
* Winzip (version 16.0 tested),
* WinRar (some issue with UTF8 characters), and
* IZArc (some issue with UTF8 characters).
= NOT-Compatible Archive Software =
These software are *not* compatible with multi-part archives:
* 7-zip, and
* the Windows Explorer embedded function.
Do not hesitate to contact me if you face some issues.
= To restore the backups =
* install a fresh version of Wordpress on your server ;
* unzip the backup (actually, the zip file comprises a plurality of files i.e. a multi-part zip (zip, z01, z02, etc.). These files should be saved in a same folder and your zip program (such as IZArc, Winzip, Winrar, ...) will do the job for you...
* If you have configured to save the entire installation, replace all the wordpress files by the one in the zip file and import the SQL files (at the root of the zip file, the files named *.sql1, *sql2, etc.) in your database (with for instance phpmyadmin). It is recommended to save your database first ;
* In other cases, replace the 'plugins', 'themes', 'uploads' folders (in the wp-content folder) with the one in the archive, replace the wp-config.php (at the root of your wordpress repository) with the one at the root of the zip file and import the SQL files (at the root of the zip file, the files named *.sql1, *sql2, etc.) in your database (with for instance phpmyadmin). It is recommended to save your database first.
= The backup files are corrupted =
Be sure that all thz zip files (i.e. .zip, .z01, z02, etc.) are in the same folder.
If you have still this issue, please try with Winzip software.
* Where can I read more?
Visit http://www.sedlex.fr/cote_geek/
InfoVersion:f450b43eebb7570fb1ec0ce188b82e8eebae57cd

View File

@@ -0,0 +1,8 @@
Contributors: Ramoonus
Donate link: http://www.ramoonus.nl
Tags: Glow, javascript, bbc,
Requires at least: 3.9
Tested up to: 4.1
Stable tag: 2.0.0-beta1
This plugin adds BBCs Glow Javascript library to your set-up.

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