Compare commits

...

155 Commits

Author SHA1 Message Date
erwanlr
1994826af8 Bumps version 2019-09-16 13:14:27 +01:00
erwanlr
ab950d6ffc Do not cache login requests - Fixes #1395 2019-09-16 10:37:43 +01:00
erwanlr
b77e611a90 Adds DFs 2019-09-14 10:35:22 +01:00
erwanlr
86f0284894 Updates help to reflect enumeration of popular plugins and themes 2019-09-13 18:10:33 +01:00
erwanlr
9bbe014dfe Merge branch 'master' of github.com:wpscanteam/wpscan 2019-09-13 17:23:19 +01:00
erwanlr
ad92c95500 Fixes crash when API returns HTML data rather than JSON in edge cases 2019-09-13 17:22:26 +01:00
Erwan
d360190382 Adds section for username enumeration in the Readme 2019-09-13 11:31:32 +02:00
ethicalhack3r
1737c8a7f6 Update readme 2019-09-13 11:02:12 +02:00
ethicalhack3r
cde262fd66 Add wpvulndb api info to readme 2019-09-13 10:49:05 +02:00
erwanlr
bd74689079 Bumps version 2019-09-13 08:34:19 +01:00
Erwan
248942bdea Updates Readme (adds link, fixes typo) 2019-09-11 11:29:45 +02:00
erwanlr
d9f203300b Updates deps 2019-09-11 10:19:48 +01:00
erwanlr
aceabc969f Merge branch 'master' into 3.7.0 2019-09-11 10:18:28 +01:00
erwanlr
dedc24d3a7 Adds DFs 2019-09-07 11:55:53 +01:00
erwanlr
6e583e78e8 Gets Sponsor text from db file 2019-09-05 21:47:26 +01:00
erwanlr
c012e83355 Merge branch 'master' into 3.7.0-merged-master 2019-09-05 19:46:16 +01:00
erwanlr
264355d185 Ignores 404 with BodyPattern DF - Ref #1386 2019-09-05 19:41:24 +01:00
erwanlr
fdbfd1ec60 Ref #1386 2019-09-03 12:03:12 +01:00
erwanlr
7a8b27a255 Fixes #1386 2019-09-02 22:09:28 +01:00
erwanlr
ec4bfac98b Adds DFs 2019-09-01 11:38:57 +01:00
erwanlr
c63ffe37c9 Updates deps and Readme 2019-08-30 18:22:25 +01:00
erwanlr
d2f3ce82c9 Fixes specs 2019-08-30 09:28:47 +01:00
erwanlr
3e24a0b0a4 Merge with master 2019-08-30 09:03:31 +01:00
Erwan
1a07e29ff4 Merge pull request #1383 from wpscanteam/dependabot/bundler/webmock-tw-3.7.0
Update webmock requirement from ~> 3.6.0 to ~> 3.7.0
2019-08-28 14:29:22 +02:00
dependabot-preview[bot]
1aa46a8928 Update webmock requirement from ~> 3.6.0 to ~> 3.7.0
Updates the requirements on [webmock](https://github.com/bblimke/webmock) to permit the latest version.
- [Release notes](https://github.com/bblimke/webmock/releases)
- [Changelog](https://github.com/bblimke/webmock/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bblimke/webmock/compare/v3.6.0...v3.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-28 09:01:28 +00:00
erwanlr
d9083f8b5f Fixes spec related to latest changes 2019-08-24 15:17:18 +01:00
erwanlr
23d558a6d7 Updates to CMSScanner 0.5.8 2019-08-24 14:49:56 +01:00
erwanlr
665a5b7b12 Adds DFs 2019-08-24 14:06:52 +01:00
erwanlr
1d73418969 Adds DFs 2019-08-17 10:36:18 +01:00
erwanlr
f67b5e4cc4 Updates deps again 2019-08-13 11:55:57 +01:00
erwanlr
ae2515444f Updates deps 2019-08-13 11:03:34 +01:00
erwanlr
463e77f0a5 VulnAPI Implementation 2019-08-13 10:03:01 +01:00
erwanlr
d7b796b1a7 Adds DFs 2019-08-10 12:22:38 +01:00
erwanlr
9b07d53077 Bumps version 2019-08-06 16:10:21 +01:00
erwanlr
8ee9b2bc31 Fixes #1378 2019-08-06 13:01:22 +01:00
erwanlr
c5989477a4 Adds DFs 2019-08-03 10:56:22 +01:00
Erwan
96d8a4e4f8 Merge pull request #1377 from wpscanteam/dependabot/bundler/rubocop-tw-0.74.0
Update rubocop requirement from ~> 0.73.0 to ~> 0.74.0
2019-08-03 10:19:49 +02:00
dependabot-preview[bot]
e865e11731 Update rubocop requirement from ~> 0.73.0 to ~> 0.74.0
Updates the requirements on [rubocop](https://github.com/rubocop-hq/rubocop) to permit the latest version.
- [Release notes](https://github.com/rubocop-hq/rubocop/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.73.0...v0.74.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-01 05:31:24 +00:00
erwanlr
f0997bfe0d Bumps version 2019-07-31 15:46:59 +01:00
erwanlr
8b67dad456 Fixes regexp perf 2019-07-31 14:54:57 +01:00
erwanlr
53fdac1038 Fixes #1376 2019-07-31 14:53:11 +01:00
erwanlr
534a7602e6 Adds DFs 2019-07-27 18:36:53 +01:00
erwanlr
30f329fe43 Bumps version 2019-07-23 18:27:09 +01:00
erwanlr
4ce39951a9 Additional specs for #1374 2019-07-23 16:33:09 +01:00
ethicalhack3r
0e9eb34626 Remove Patreon link 2019-07-23 12:09:04 +02:00
erwanlr
0ff299c425 Updates UA used when updating the DB 2019-07-22 12:13:01 +01:00
erwanlr
6366258ce9 Merge branch 'df' 2019-07-20 19:11:06 +01:00
erwanlr
bca69a026e Adds DFs 2019-07-20 19:10:47 +01:00
Christian Mehlmauer
adc26ea42a ruby 2.6.3 2019-07-19 09:16:56 +02:00
erwanlr
84422b10c8 Changes db_data to metadata 2019-07-18 18:52:52 +01:00
erwanlr
d05ad0f8f4 Adds an Api Token controller 2019-07-18 17:40:27 +01:00
erwanlr
3f70ddaffa Switching to dev 2019-07-18 17:31:02 +01:00
Erwan
b16e8d84d7 Merge pull request #1369 from wpscanteam/dependabot/bundler/rubocop-tw-0.73.0
Update rubocop requirement from ~> 0.72.0 to ~> 0.73.0
2019-07-17 11:38:45 +02:00
dependabot-preview[bot]
5ee405d5a0 Update rubocop requirement from ~> 0.72.0 to ~> 0.73.0
Updates the requirements on [rubocop](https://github.com/rubocop-hq/rubocop) to permit the latest version.
- [Release notes](https://github.com/rubocop-hq/rubocop/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.72.0...v0.73.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-17 05:28:25 +00:00
erwanlr
a5b9470636 Adds DFs 2019-07-13 11:57:18 +01:00
erwanlr
16a3d54cb6 Bumps version 2019-07-11 17:50:46 +01:00
erwanlr
9677dcd978 Makes sure the sub_dir is only checked once 2019-07-10 18:35:46 +01:00
erwanlr
17ea42f918 Updates cms_scanner dep 2019-07-10 09:17:49 +01:00
erwanlr
bd8915918d Switcing back to master 2019-07-09 15:07:42 +01:00
erwanlr
91db6773a0 Reduces --themes-threshold 2019-07-09 14:37:30 +01:00
erwanlr
f50680b61f Adds a --plugins-threshold and --themes-threshold options 2019-07-08 19:47:46 +01:00
erwanlr
3fb5d33333 Switching to dev 2019-07-08 17:34:13 +01:00
erwanlr
f70bbb2660 Switching to master 2019-07-08 14:02:38 +01:00
erwanlr
589c1ac9bb Only create Versions DF when needed 2019-07-08 13:02:29 +01:00
erwanlr
d458fa1b89 Switching to dev 2019-07-08 10:23:42 +01:00
erwanlr
dc2c99434f Switching to master 2019-07-07 12:19:05 +01:00
erwanlr
bbf36562d0 Fixes specs 2019-07-07 09:57:54 +01:00
erwanlr
c458edf3e4 Adds a note about the Readme DF 2019-07-07 08:51:29 +01:00
erwanlr
99c2aaef7a Changes some DF method names to avoid confusion with db ones 2019-07-07 08:35:41 +01:00
erwanlr
921096ca10 Adds DFs 2019-07-07 08:09:22 +01:00
erwanlr
b0fbd6fa36 Removes empty expected DF 2019-07-06 15:58:23 +01:00
erwanlr
21bd67c44f Switching to minor release 2019-07-06 15:53:00 +01:00
erwanlr
4f142985a2 Fixes #1364 2019-07-06 15:32:42 +01:00
erwanlr
bfa89b44bc Switching to dev 2019-07-06 13:04:38 +01:00
erwanlr
eba876e72b Adds DFs 2019-07-06 11:05:22 +01:00
erwanlr
f1a7413e20 Adds Theme DFs 2019-07-05 09:34:13 +01:00
erwanlr
4d32749489 Updates deps and bump version 2019-07-04 17:06:28 +01:00
erwanlr
d911a16684 Removes useless WPScan namespace 2019-07-04 16:46:33 +01:00
erwanlr
d7193bc755 Ref #1364 2019-07-04 16:33:14 +01:00
erwanlr
aee9ffdb9c Fixes #1365 2019-07-04 15:45:01 +01:00
erwanlr
1f627d5e49 Adds DFs 2019-06-29 10:51:03 +01:00
ethicalhack3r
bb67626d09 Change thimthub enum icon back to info 2019-06-28 15:04:30 +02:00
ethicalhack3r
4e0153e94a Change some enum icons to critical 2019-06-28 15:00:05 +02:00
erwanlr
065142ff19 Updates Rubocop dep 2019-06-27 08:15:54 +01:00
erwanlr
8bb6fae52f Adds DFs 2019-06-22 11:31:28 +01:00
Erwan
8cb7b81903 Merge pull request #1359 from wpscanteam/dependabot/bundler/rubocop-performance-tw-1.4.0
Update rubocop-performance requirement from ~> 1.3.0 to ~> 1.4.0
2019-06-21 13:02:00 +02:00
dependabot-preview[bot]
cb214ccda9 Update rubocop-performance requirement from ~> 1.3.0 to ~> 1.4.0
Updates the requirements on [rubocop-performance](https://github.com/rubocop-hq/rubocop-performance) to permit the latest version.
- [Release notes](https://github.com/rubocop-hq/rubocop-performance/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop-performance/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop-performance/compare/v1.3.0...v1.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-21 05:41:30 +00:00
erwanlr
3fa7b96f27 Bumps version 2019-06-17 18:11:37 +01:00
erwanlr
7c8e259072 Adds DFs 2019-06-15 16:28:14 +01:00
erwanlr
743d067042 Adds DFs 2019-06-08 12:11:24 +01:00
Erwan
50ea410718 Merge pull request #1353 from wpscanteam/dependabot/bundler/webmock-tw-3.6.0
Update webmock requirement from ~> 3.5.1 to ~> 3.6.0
2019-06-05 12:50:44 +02:00
dependabot-preview[bot]
e71182aed2 Update webmock requirement from ~> 3.5.1 to ~> 3.6.0
Updates the requirements on [webmock](https://github.com/bblimke/webmock) to permit the latest version.
- [Release notes](https://github.com/bblimke/webmock/releases)
- [Changelog](https://github.com/bblimke/webmock/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bblimke/webmock/compare/v3.5.1...v3.6.0)
2019-06-05 07:53:59 +00:00
erwanlr
97f7963e0b Adds DFs 2019-06-02 14:05:44 +01:00
Erwan
6cea6a10bd Merge pull request #1352 from wpscanteam/dependabot/bundler/rubocop-tw-0.71.0
Update rubocop requirement from ~> 0.70.0 to ~> 0.71.0
2019-05-31 14:24:27 +02:00
dependabot-preview[bot]
344d41e365 Update rubocop requirement from ~> 0.70.0 to ~> 0.71.0
Updates the requirements on [rubocop](https://github.com/rubocop-hq/rubocop) to permit the latest version.
- [Release notes](https://github.com/rubocop-hq/rubocop/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.70.0...v0.71.0)
2019-05-31 08:04:55 +00:00
erwanlr
597a8adfed Removes FP DF 2019-05-24 11:16:52 +01:00
erwanlr
5682e5483a Adds DFs 2019-05-24 11:06:50 +01:00
Erwan
18779edd7d Merge pull request #1349 from wpscanteam/dependabot/bundler/rubocop-tw-0.70.0
Update rubocop requirement from ~> 0.69.0 to ~> 0.70.0
2019-05-22 14:23:49 +02:00
dependabot[bot]
63aeaea77a Update rubocop requirement from ~> 0.69.0 to ~> 0.70.0
Updates the requirements on [rubocop](https://github.com/rubocop-hq/rubocop) to permit the latest version.
- [Release notes](https://github.com/rubocop-hq/rubocop/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.69.0...v0.70.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-22 06:25:23 +00:00
Erwan
f51e48cb40 Merge pull request #1347 from wpscanteam/dependabot/bundler/rubocop-tw-0.69.0
Update rubocop requirement from ~> 0.68.0 to ~> 0.69.0
2019-05-14 17:50:34 +02:00
Erwan
193372c79c Adds a note about Nokogiri in the Readme 2019-05-14 14:12:28 +02:00
dependabot[bot]
34d0afe7e5 Update rubocop requirement from ~> 0.68.0 to ~> 0.69.0
Updates the requirements on [rubocop](https://github.com/rubocop-hq/rubocop) to permit the latest version.
- [Release notes](https://github.com/rubocop-hq/rubocop/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.68.0...v0.69.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-14 11:26:28 +00:00
Erwan
d33a9dd56d Merge pull request #1346 from wpscanteam/dependabot/bundler/rubocop-performance-tw-1.3.0
Update rubocop-performance requirement from ~> 1.2.0 to ~> 1.3.0
2019-05-14 13:19:26 +02:00
dependabot[bot]
af2be90176 Update rubocop-performance requirement from ~> 1.2.0 to ~> 1.3.0
Updates the requirements on [rubocop-performance](https://github.com/rubocop-hq/rubocop-performance) to permit the latest version.
- [Release notes](https://github.com/rubocop-hq/rubocop-performance/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop-performance/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop-performance/compare/v1.2.0...v1.3.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-14 06:21:57 +00:00
erwanlr
701fb21544 Adds DFs 2019-05-11 11:34:47 +01:00
erwanlr
c8f010d9a6 Ref #1344 2019-05-08 18:17:25 +01:00
Erwan
c1ca7580e2 Merge pull request #1343 from wpscanteam/dependabot/bundler/rubocop-performance-tw-1.2.0
Update rubocop-performance requirement from ~> 1.1.0 to ~> 1.2.0
2019-05-08 18:56:36 +02:00
dependabot[bot]
11d3c2cbf1 Update rubocop-performance requirement from ~> 1.1.0 to ~> 1.2.0
Updates the requirements on [rubocop-performance](https://github.com/rubocop-hq/rubocop-performance) to permit the latest version.
- [Release notes](https://github.com/rubocop-hq/rubocop-performance/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop-performance/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop-performance/compare/v1.1.0...v1.2.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-06 06:12:47 +00:00
erwanlr
412f576aee Adds DFs 2019-05-03 11:54:25 +01:00
erwanlr
ff98a7b23b Fixes #1341 2019-05-01 19:50:43 +01:00
erwanlr
507bac8542 Merge branch 'master' of github.com:wpscanteam/wpscan 2019-04-29 15:48:07 +01:00
erwanlr
3bd6cf4805 Adds Ruby 2.6.3 to Travis 2019-04-29 15:47:55 +01:00
erwanlr
5712b31869 Updates Rubocop dep 2019-04-29 15:47:33 +01:00
Erwan
b0f9a0b18f Update issue templates 2019-04-29 15:24:22 +02:00
Erwan
f7665b460e Update issue templates 2019-04-29 15:20:44 +02:00
Erwan
100029b640 Delete old issue template 2019-04-29 15:18:10 +02:00
Erwan
2b89bddf0f Update issue templates 2019-04-29 15:17:39 +02:00
erwanlr
ca46bad8ec Bumps version 2019-04-26 11:53:00 +01:00
erwanlr
1ecd2600a3 Adds DFs 2019-04-26 10:56:26 +01:00
erwanlr
28306b126b Adds DFs 2019-04-24 17:32:25 +01:00
erwanlr
5c842e192b Updates deps 2019-04-24 12:42:18 +01:00
erwanlr
f9f307118d Adds DFs 2019-04-19 10:18:52 +01:00
erwanlr
2266fa4f4b Removes useless comment 2019-04-18 14:43:21 +01:00
erwanlr
6df2564d1a Improves Target#wordpress_hosted? 2019-04-18 14:17:00 +01:00
erwanlr
b2a62ebd26 Fixes #1335 2019-04-18 12:26:47 +01:00
erwanlr
2fca30752a Improves wp-content detection 2019-04-18 12:13:56 +01:00
erwanlr
210eced369 Typo in comments 2019-04-17 17:39:20 +01:00
erwanlr
08c574aff8 Improves detection of wp-content folder 2019-04-17 15:52:06 +01:00
Christian Mehlmauer
f4db2d65f1 fix #1309 2019-04-16 06:42:26 +02:00
erwanlr
23b02ade96 Adds DFs 2019-04-13 14:29:24 +01:00
erwanlr
71d35b16ac Adds DFs 2019-04-13 14:22:42 +01:00
erwanlr
200058c52a Adds DFs 2019-04-13 10:37:39 +01:00
erwanlr
edb5fb202a Removes check for 301 when enumerating plugins and themes 2019-04-13 08:13:38 +01:00
erwanlr
d114c25cdb Typo 2019-04-12 14:52:39 +01:00
erwanlr
64e469568b Adds message help for --wp-content-dir and --wp-plugins-dir options 2019-04-12 14:07:28 +01:00
erwanlr
c63d777372 Fixes version wrongly bumped 2019-04-12 14:00:39 +01:00
erwanlr
ae343b8cb0 Checks for wp-content directly (depends on detection-mode) when not identified passively 2019-04-12 13:55:40 +01:00
Erwan
86eb5d2d57 Merge pull request #1332 from wpscanteam/dependabot/bundler/rspec-its-tw-1.3.0
Update rspec-its requirement from ~> 1.2.0 to ~> 1.3.0
2019-04-10 10:33:27 +02:00
dependabot[bot]
b562d241db Update rspec-its requirement from ~> 1.2.0 to ~> 1.3.0
Updates the requirements on [rspec-its](https://github.com/rspec/rspec-its) to permit the latest version.
- [Release notes](https://github.com/rspec/rspec-its/releases)
- [Changelog](https://github.com/rspec/rspec-its/blob/master/Changelog.md)
- [Commits](https://github.com/rspec/rspec-its/compare/v1.2.0...v1.3.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-10 06:07:39 +00:00
erwanlr
49b1829b78 Bumps version 2019-04-08 16:58:26 +01:00
erwanlr
1a5bf4035c Update deps 2019-04-08 09:39:07 +01:00
erwanlr
f3810a1504 Bumps version 2019-04-07 17:45:29 +01:00
erwanlr
4831760c11 Merge branch '3.5.1' 2019-04-07 17:42:51 +01:00
erwanlr
f375d8991e Update deps 2019-04-07 17:35:18 +01:00
erwanlr
8145a4a3a6 Fixes #1330 2019-04-07 17:06:19 +01:00
erwanlr
12c9b49d4c Adds DFs 2019-04-06 11:34:23 +01:00
erwanlr
c8eb81161e Uses https rather than git protocols for CMSScanner dep 2019-04-05 19:53:29 +01:00
erwanlr
8ab246a66c Uses CMSScanner git dep 2019-04-05 19:48:22 +01:00
erwanlr
8dfc4797fa Handles default user_agent_list via CLI option (in CMSScanner) 2019-04-05 19:30:53 +01:00
erwanlr
7888fe1176 Uses ParsedCli 2019-04-05 16:47:14 +01:00
Erwan
8a6f3056a3 Merge pull request #1329 from wpscanteam/dependabot/bundler/rubocop-tw-0.67.1
Update rubocop requirement from ~> 0.66.0 to ~> 0.67.1
2019-04-05 11:37:00 +02:00
dependabot[bot]
5fbdf9e013 Update rubocop requirement from ~> 0.66.0 to ~> 0.67.1
Updates the requirements on [rubocop](https://github.com/rubocop-hq/rubocop) to permit the latest version.
- [Release notes](https://github.com/rubocop-hq/rubocop/releases)
- [Changelog](https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop-hq/rubocop/compare/v0.66.0...v0.67.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-05 06:16:13 +00:00
erwanlr
1da2f5e823 Sets the Target#mu_plugind to true when detected passively 2019-04-04 17:25:58 +01:00
erwanlr
888779f81b Support of Ruby 2.3 removed as its life ended 2019-04-04 15:40:21 +01:00
erwanlr
352286e497 Adds a #maybe_add_cookies to handle website requiring a specific cookie 2019-04-03 19:08:52 +01:00
442 changed files with 113019 additions and 736 deletions

View File

@@ -1,3 +1,14 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
Before submitting an issue, please make sure you fully read any potential error messages output and did some research on your own.
### Subject of the issue ### Subject of the issue
Describe your issue here. Describe your issue here.
@@ -24,4 +35,4 @@ Things you have tried (where relevant):
* Update Ruby to the latest version [ ] * Update Ruby to the latest version [ ]
* Ensure you can reach the target site using cURL [ ] * Ensure you can reach the target site using cURL [ ]
* Proxied WPScan through a HTTP proxy to view the raw traffic [ ] * Proxied WPScan through a HTTP proxy to view the raw traffic [ ]
* Ensure you are using a supported Operating System (Linux and macOS) [ ] * Ensure you are using a supported Operating System (Linux and macOS) [ ]

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

10
.github/ISSUE_TEMPLATE/other-issue.md vendored Normal file
View File

@@ -0,0 +1,10 @@
---
name: Other Issue
about: Create a report which is not a related to a Bug or Feature
title: ''
labels: ''
assignees: ''
---
Before submitting an issue, please make sure you fully read any potential error messages output and did some research on your own.

View File

@@ -1,5 +1,6 @@
require: rubocop-performance
AllCops: AllCops:
TargetRubyVersion: 2.3 TargetRubyVersion: 2.4
Exclude: Exclude:
- '*.gemspec' - '*.gemspec'
- 'vendor/**/*' - 'vendor/**/*'
@@ -7,10 +8,12 @@ ClassVars:
Enabled: false Enabled: false
LineLength: LineLength:
Max: 120 Max: 120
MethodLength:
Max: 20
Lint/UriEscapeUnescape: Lint/UriEscapeUnescape:
Enabled: false Enabled: false
MethodLength:
Max: 20
Exclude:
- 'app/controllers/enumeration/cli_options.rb'
Metrics/AbcSize: Metrics/AbcSize:
Max: 25 Max: 25
Metrics/BlockLength: Metrics/BlockLength:
@@ -18,9 +21,14 @@ Metrics/BlockLength:
- 'spec/**/*' - 'spec/**/*'
Metrics/ClassLength: Metrics/ClassLength:
Max: 150 Max: 150
Exclude:
- 'app/controllers/enumeration/cli_options.rb'
Metrics/CyclomaticComplexity: Metrics/CyclomaticComplexity:
Max: 8 Max: 8
Style/Documentation: Style/Documentation:
Enabled: false Enabled: false
Style/FormatStringToken: Style/FormatStringToken:
Enabled: false Enabled: false
Style/NumericPredicate:
Exclude:
- 'app/controllers/vuln_api.rb'

View File

@@ -2,20 +2,12 @@ language: ruby
sudo: false sudo: false
cache: bundler cache: bundler
rvm: rvm:
- 2.3.0
- 2.3.1
- 2.3.2
- 2.3.3
- 2.3.4
- 2.3.5
- 2.3.6
- 2.3.7
- 2.3.8
- 2.4.1 - 2.4.1
- 2.4.2 - 2.4.2
- 2.4.3 - 2.4.3
- 2.4.4 - 2.4.4
- 2.4.5 - 2.4.5
- 2.4.6
- 2.5.0 - 2.5.0
- 2.5.1 - 2.5.1
- 2.5.2 - 2.5.2
@@ -25,6 +17,7 @@ rvm:
- 2.6.0 - 2.6.0
- 2.6.1 - 2.6.1
- 2.6.2 - 2.6.2
- 2.6.3
- ruby-head - ruby-head
before_install: before_install:
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc" - "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"

View File

@@ -1,4 +1,4 @@
FROM ruby:2.6.2-alpine3.9 AS builder FROM ruby:2.6.3-alpine AS builder
LABEL maintainer="WPScan Team <team@wpscan.org>" LABEL maintainer="WPScan Team <team@wpscan.org>"
ARG BUNDLER_ARGS="--jobs=8 --without test development" ARG BUNDLER_ARGS="--jobs=8 --without test development"
@@ -19,19 +19,22 @@ RUN rake install --trace
RUN chmod -R a+r /usr/local/bundle RUN chmod -R a+r /usr/local/bundle
FROM ruby:2.6.2-alpine3.9 FROM ruby:2.6.3-alpine
LABEL maintainer="WPScan Team <team@wpscan.org>" LABEL maintainer="WPScan Team <team@wpscan.org>"
RUN adduser -h /wpscan -g WPScan -D wpscan RUN adduser -h /wpscan -g WPScan -D wpscan
COPY --from=builder /usr/local/bundle /usr/local/bundle COPY --from=builder /usr/local/bundle /usr/local/bundle
RUN chown -R wpscan:wpscan /wpscan RUN chown -R wpscan:wpscan /wpscan
# runtime dependencies # runtime dependencies
RUN apk add --no-cache libcurl procps sqlite-libs RUN apk add --no-cache libcurl procps sqlite-libs
WORKDIR /wpscan
USER wpscan USER wpscan
RUN /usr/local/bundle/bin/wpscan --update --verbose RUN /usr/local/bundle/bin/wpscan --update --verbose
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"] ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]

View File

@@ -2,3 +2,5 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gemspec gemspec
# gem 'cms_scanner', branch: 'xxx', git: 'https://github.com/wpscanteam/CMSScanner.git'

View File

@@ -17,7 +17,6 @@
<a href="https://badge.fury.io/rb/wpscan" target="_blank"><img src="https://badge.fury.io/rb/wpscan.svg"></a> <a href="https://badge.fury.io/rb/wpscan" target="_blank"><img src="https://badge.fury.io/rb/wpscan.svg"></a>
<a href="https://travis-ci.org/wpscanteam/wpscan" target="_blank"><img src="https://travis-ci.org/wpscanteam/wpscan.svg?branch=master"></a> <a href="https://travis-ci.org/wpscanteam/wpscan" target="_blank"><img src="https://travis-ci.org/wpscanteam/wpscan.svg?branch=master"></a>
<a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a> <a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
<a href="https://www.patreon.com/wpscan" target="_blank"><img src="https://img.shields.io/badge/patreon-donate-green.svg"></a>
</p> </p>
# INSTALL # INSTALL
@@ -30,6 +29,7 @@
- Curl >= 7.21 - Recommended: latest - Curl >= 7.21 - Recommended: latest
- The 7.29 has a segfault - The 7.29 has a segfault
- RubyGems - Recommended: latest - RubyGems - Recommended: latest
- Nokogiri might require packages to be installed via your package manager depending on your OS, see https://nokogiri.org/tutorials/installing_nokogiri.html
### From RubyGems (Recommended) ### From RubyGems (Recommended)
@@ -77,41 +77,60 @@ docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u1-1
# Usage # Usage
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings. If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used. ```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings.
If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
As a result, when using the ```--enumerate``` option, don't forget to set the ```--plugins-detection``` accordingly, as its default is 'passive'. As a result, when using the ```--enumerate``` option, don't forget to set the ```--plugins-detection``` accordingly, as its default is 'passive'.
For more options, open a terminal and type ```wpscan --help``` (if you built wpscan from the source, you should type the command outside of the git repo) For more options, open a terminal and type ```wpscan --help``` (if you built wpscan from the source, you should type the command outside of the git repo)
The DB is located at ~/.wpscan/db The DB is located at ~/.wpscan/db
## Vulnerability Database
The WPScan CLI tool uses the [WPVulnDB API](https://wpvulndb.com/api) to retrieve WordPress vulnerability data in real time. For WPScan to retrieve the vulnerability data an API token must be supplied via the `--api-token` option, or via a configuration file, as discussed below. An API token can be obtained by registering an account on [WPVulnDB](https://wpvulndb.com/users/sign_up). Up to 50 API requests per day are given free of charge to registered users. Once the 50 API requests are exhausted, WPScan will continue to work as normal but without any vulnerability data. Users can upgrade to paid API usage to increase their API limits within their user profile on [WPVulnDB](https://wpvulndb.com/).
## Load CLI options from file/s
WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last): WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
- ~/.wpscan/cli_options.json - ~/.wpscan/scan.json
- ~/.wpscan/cli_options.yml - ~/.wpscan/scan.yml
- pwd/.wpscan/cli_options.json - pwd/.wpscan/scan.json
- pwd/.wpscan/cli_options.yml - pwd/.wpscan/scan.yml
If those files exist, options from them will be loaded and overridden if found twice. If those files exist, options from the `cli_options` key will be loaded and overridden if found twice.
e.g: e.g:
~/.wpscan/cli_options.yml: ~/.wpscan/scan.yml:
```yml ```yml
proxy: 'http://127.0.0.1:8080' cli_options:
verbose: true proxy: 'http://127.0.0.1:8080'
verbose: true
``` ```
pwd/.wpscan/cli_options.yml: pwd/.wpscan/scan.yml:
```yml ```yml
proxy: 'socks5://127.0.0.1:9090' cli_options:
url: 'http://target.tld' proxy: 'socks5://127.0.0.1:9090'
url: 'http://target.tld'
``` ```
Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld``` Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
Enumerating usernames ## Save API Token in a file
The feature mentioned above is useful to keep the API Token in a config file and not have to supply it via the CLI each time. To do so, create the ~/.wpscan/scan.yml file containing the below:
```yml
cli_options:
api_token: YOUR_API_TOKEN
```
## Enumerating usernames
```shell ```shell
wpscan --url https://target.tld/ --enumerate u wpscan --url https://target.tld/ --enumerate u

View File

@@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'controllers/core' require_relative 'controllers/core'
require_relative 'controllers/vuln_api'
require_relative 'controllers/custom_directories' require_relative 'controllers/custom_directories'
require_relative 'controllers/wp_version' require_relative 'controllers/wp_version'
require_relative 'controllers/main_theme' require_relative 'controllers/main_theme'

View File

@@ -27,38 +27,41 @@ module WPScan
# @return [ Boolean ] # @return [ Boolean ]
def update_db_required? def update_db_required?
if local_db.missing_files? if local_db.missing_files?
raise Error::MissingDatabaseFile if parsed_options[:update] == false raise Error::MissingDatabaseFile if ParsedCli.update == false
return true return true
end end
return parsed_options[:update] unless parsed_options[:update].nil? return ParsedCli.update unless ParsedCli.update.nil?
return false unless user_interaction? && local_db.outdated? return false unless user_interaction? && local_db.outdated?
output('@notice', msg: 'It seems like you have not updated the database for some time.') output('@notice', msg: 'It seems like you have not updated the database for some time.')
print '[?] Do you want to update now? [Y]es [N]o, default: [N]' print '[?] Do you want to update now? [Y]es [N]o, default: [N]'
Readline.readline =~ /^y/i ? true : false /^y/i.match?(Readline.readline) ? true : false
end end
def update_db def update_db
output('db_update_started') output('db_update_started')
output('db_update_finished', updated: local_db.update, verbose: parsed_options[:verbose]) output('db_update_finished', updated: local_db.update, verbose: ParsedCli.verbose)
exit(0) unless parsed_options[:url] exit(0) unless ParsedCli.url
end end
def before_scan def before_scan
@last_update = local_db.last_update @last_update = local_db.last_update
maybe_output_banner_help_and_version # From CMS Scanner maybe_output_banner_help_and_version # From CMSScanner
update_db if update_db_required? update_db if update_db_required?
setup_cache setup_cache
check_target_availability check_target_availability
load_server_module load_server_module
check_wordpress_state check_wordpress_state
rescue Error::NotWordPress => e
target.maybe_add_cookies
raise e unless target.wordpress?(ParsedCli.detection_mode)
end end
# Raises errors if the target is hosted on wordpress.com or is not running WordPress # Raises errors if the target is hosted on wordpress.com or is not running WordPress
@@ -66,14 +69,14 @@ module WPScan
def check_wordpress_state def check_wordpress_state
raise Error::WordPressHosted if target.wordpress_hosted? raise Error::WordPressHosted if target.wordpress_hosted?
if Addressable::URI.parse(target.homepage_url).path =~ %r{/wp-admin/install.php$}i if %r{/wp-admin/install.php$}i.match?(Addressable::URI.parse(target.homepage_url).path)
output('not_fully_configured', url: target.homepage_url) output('not_fully_configured', url: target.homepage_url)
exit(WPScan::ExitCode::VULNERABLE) exit(WPScan::ExitCode::VULNERABLE)
end end
raise Error::NotWordPress unless target.wordpress?(parsed_options[:detection_mode]) || parsed_options[:force] raise Error::NotWordPress unless target.wordpress?(ParsedCli.detection_mode) || ParsedCli.force
end end
# Loads the related server module in the target # Loads the related server module in the target
@@ -85,7 +88,7 @@ module WPScan
server = target.server || :Apache # Tries to auto detect the server server = target.server || :Apache # Tries to auto detect the server
# Force a specific server module to be loaded if supplied # Force a specific server module to be loaded if supplied
case parsed_options[:server] case ParsedCli.server
when :apache when :apache
server = :Apache server = :Apache
when :iis when :iis

View File

@@ -7,16 +7,18 @@ module WPScan
class CustomDirectories < CMSScanner::Controller::Base class CustomDirectories < CMSScanner::Controller::Base
def cli_options def cli_options
[ [
OptString.new(['--wp-content-dir DIR']), OptString.new(['--wp-content-dir DIR',
OptString.new(['--wp-plugins-dir DIR']) 'The wp-content directory if custom or not detected, such as "wp-content"']),
OptString.new(['--wp-plugins-dir DIR',
'The plugins directory if custom or not detected, such as "wp-content/plugins"'])
] ]
end end
def before_scan def before_scan
target.content_dir = parsed_options[:wp_content_dir] if parsed_options[:wp_content_dir] target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir
target.plugins_dir = parsed_options[:wp_plugins_dir] if parsed_options[:wp_plugins_dir] target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir
return if target.content_dir return if target.content_dir(ParsedCli.detection_mode)
raise Error::WpContentDirNotDetected raise Error::WpContentDirNotDetected
end end

View File

@@ -7,17 +7,8 @@ module WPScan
module Controller module Controller
# Enumeration Controller # Enumeration Controller
class Enumeration < CMSScanner::Controller::Base class Enumeration < CMSScanner::Controller::Base
def before_scan
DB::DynamicFinders::Plugin.create_versions_finders
DB::DynamicFinders::Theme.create_versions_finders
# Force the Garbage Collector to run due to the above method being
# quite heavy in objects allocation
GC.start
end
def run def run
enum = parsed_options[:enumerate] || {} enum = ParsedCli.enumerate || {}
enum_plugins if enum_plugins?(enum) enum_plugins if enum_plugins?(enum)
enum_themes if enum_themes?(enum) enum_themes if enum_themes?(enum)

View File

@@ -11,7 +11,6 @@ module WPScan
end end
# @return [ Array<OptParseValidator::OptBase> ] # @return [ Array<OptParseValidator::OptBase> ]
# rubocop:disable Metrics/MethodLength
def cli_enum_choices def cli_enum_choices
[ [
OptMultiChoices.new( OptMultiChoices.new(
@@ -19,10 +18,10 @@ module WPScan
choices: { choices: {
vp: OptBoolean.new(['--vulnerable-plugins']), vp: OptBoolean.new(['--vulnerable-plugins']),
ap: OptBoolean.new(['--all-plugins']), ap: OptBoolean.new(['--all-plugins']),
p: OptBoolean.new(['--plugins']), p: OptBoolean.new(['--popular-plugins']),
vt: OptBoolean.new(['--vulnerable-themes']), vt: OptBoolean.new(['--vulnerable-themes']),
at: OptBoolean.new(['--all-themes']), at: OptBoolean.new(['--all-themes']),
t: OptBoolean.new(['--themes']), t: OptBoolean.new(['--popular-themes']),
tt: OptBoolean.new(['--timthumbs']), tt: OptBoolean.new(['--timthumbs']),
cb: OptBoolean.new(['--config-backups']), cb: OptBoolean.new(['--config-backups']),
dbe: OptBoolean.new(['--db-exports']), dbe: OptBoolean.new(['--db-exports']),
@@ -45,7 +44,6 @@ module WPScan
) )
] ]
end end
# rubocop:enable Metrics/MethodLength
# @return [ Array<OptParseValidator::OptBase> ] # @return [ Array<OptParseValidator::OptBase> ]
def cli_plugins_opts def cli_plugins_opts
@@ -67,6 +65,11 @@ module WPScan
'Use the supplied mode to check plugins versions instead of the --detection-mode ' \ 'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
'or --plugins-detection modes.'], 'or --plugins-detection modes.'],
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
),
OptInteger.new(
['--plugins-threshold THRESHOLD',
'Raise an error when the number of detected plugins via known locations reaches the threshold. ' \
'Set to 0 to ignore the threshold.'], default: 100
) )
] ]
end end
@@ -91,6 +94,11 @@ module WPScan
'Use the supplied mode to check themes versions instead of the --detection-mode ' \ 'Use the supplied mode to check themes versions instead of the --detection-mode ' \
'or --themes-detection modes.'], 'or --themes-detection modes.'],
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
),
OptInteger.new(
['--themes-threshold THRESHOLD',
'Raise an error when the number of detected themes via known locations reaches the threshold. ' \
'Set to 0 to ignore the threshold.'], default: 20
) )
] ]
end end

View File

@@ -7,13 +7,13 @@ module WPScan
# @param [ String ] type (plugins or themes) # @param [ String ] type (plugins or themes)
# @param [ Symbol ] detection_mode # @param [ Symbol ] detection_mode
# #
# @return [ String ] The related enumration message depending on the parsed_options and type supplied # @return [ String ] The related enumration message depending on the ParsedCli and type supplied
def enum_message(type, detection_mode) def enum_message(type, detection_mode)
return unless %w[plugins themes].include?(type) return unless %w[plugins themes].include?(type)
details = if parsed_options[:enumerate][:"vulnerable_#{type}"] details = if ParsedCli.enumerate[:"vulnerable_#{type}"]
'Vulnerable' 'Vulnerable'
elsif parsed_options[:enumerate][:"all_#{type}"] elsif ParsedCli.enumerate[:"all_#{type}"]
'All' 'All'
else else
'Most Popular' 'Most Popular'
@@ -39,15 +39,15 @@ module WPScan
# #
# @return [ Hash ] # @return [ Hash ]
def default_opts(type) def default_opts(type)
mode = parsed_options[:"#{type}_detection"] || parsed_options[:detection_mode] mode = ParsedCli.options[:"#{type}_detection"] || ParsedCli.detection_mode
{ {
mode: mode, mode: mode,
exclude_content: parsed_options[:exclude_content_based], exclude_content: ParsedCli.exclude_content_based,
show_progression: user_interaction?, show_progression: user_interaction?,
version_detection: { version_detection: {
mode: parsed_options[:"#{type}_version_detection"] || mode, mode: ParsedCli.options[:"#{type}_version_detection"] || mode,
confidence_threshold: parsed_options[:"#{type}_version_all"] ? 0 : 100 confidence_threshold: ParsedCli.options[:"#{type}_version_all"] ? 0 : 100
} }
} }
end end
@@ -56,12 +56,13 @@ module WPScan
# #
# @return [ Boolean ] Wether or not to enumerate the plugins # @return [ Boolean ] Wether or not to enumerate the plugins
def enum_plugins?(opts) def enum_plugins?(opts)
opts[:plugins] || opts[:all_plugins] || opts[:vulnerable_plugins] opts[:popular_plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
end end
def enum_plugins def enum_plugins
opts = default_opts('plugins').merge( opts = default_opts('plugins').merge(
list: plugins_list_from_opts(parsed_options), list: plugins_list_from_opts(ParsedCli.options),
threshold: ParsedCli.plugins_threshold,
sort: true sort: true
) )
@@ -77,7 +78,7 @@ module WPScan
plugins.each(&:version) plugins.each(&:version)
plugins.select!(&:vulnerable?) if parsed_options[:enumerate][:vulnerable_plugins] plugins.select!(&:vulnerable?) if ParsedCli.enumerate[:vulnerable_plugins]
output('plugins', plugins: plugins) output('plugins', plugins: plugins)
end end
@@ -91,7 +92,7 @@ module WPScan
if opts[:enumerate][:all_plugins] if opts[:enumerate][:all_plugins]
DB::Plugins.all_slugs DB::Plugins.all_slugs
elsif opts[:enumerate][:plugins] elsif opts[:enumerate][:popular_plugins]
DB::Plugins.popular_slugs DB::Plugins.popular_slugs
else else
DB::Plugins.vulnerable_slugs DB::Plugins.vulnerable_slugs
@@ -102,12 +103,13 @@ module WPScan
# #
# @return [ Boolean ] Wether or not to enumerate the themes # @return [ Boolean ] Wether or not to enumerate the themes
def enum_themes?(opts) def enum_themes?(opts)
opts[:themes] || opts[:all_themes] || opts[:vulnerable_themes] opts[:popular_themes] || opts[:all_themes] || opts[:vulnerable_themes]
end end
def enum_themes def enum_themes
opts = default_opts('themes').merge( opts = default_opts('themes').merge(
list: themes_list_from_opts(parsed_options), list: themes_list_from_opts(ParsedCli.options),
threshold: ParsedCli.themes_threshold,
sort: true sort: true
) )
@@ -123,7 +125,7 @@ module WPScan
themes.each(&:version) themes.each(&:version)
themes.select!(&:vulnerable?) if parsed_options[:enumerate][:vulnerable_themes] themes.select!(&:vulnerable?) if ParsedCli.enumerate[:vulnerable_themes]
output('themes', themes: themes) output('themes', themes: themes)
end end
@@ -137,7 +139,7 @@ module WPScan
if opts[:enumerate][:all_themes] if opts[:enumerate][:all_themes]
DB::Themes.all_slugs DB::Themes.all_slugs
elsif opts[:enumerate][:themes] elsif opts[:enumerate][:popular_themes]
DB::Themes.popular_slugs DB::Themes.popular_slugs
else else
DB::Themes.vulnerable_slugs DB::Themes.vulnerable_slugs
@@ -145,28 +147,28 @@ module WPScan
end end
def enum_timthumbs def enum_timthumbs
opts = default_opts('timthumbs').merge(list: parsed_options[:timthumbs_list]) opts = default_opts('timthumbs').merge(list: ParsedCli.timthumbs_list)
output('@info', msg: "Enumerating Timthumbs #{enum_detection_message(opts[:mode])}") if user_interaction? output('@info', msg: "Enumerating Timthumbs #{enum_detection_message(opts[:mode])}") if user_interaction?
output('timthumbs', timthumbs: target.timthumbs(opts)) output('timthumbs', timthumbs: target.timthumbs(opts))
end end
def enum_config_backups def enum_config_backups
opts = default_opts('config_backups').merge(list: parsed_options[:config_backups_list]) opts = default_opts('config_backups').merge(list: ParsedCli.config_backups_list)
output('@info', msg: "Enumerating Config Backups #{enum_detection_message(opts[:mode])}") if user_interaction? output('@info', msg: "Enumerating Config Backups #{enum_detection_message(opts[:mode])}") if user_interaction?
output('config_backups', config_backups: target.config_backups(opts)) output('config_backups', config_backups: target.config_backups(opts))
end end
def enum_db_exports def enum_db_exports
opts = default_opts('db_exports').merge(list: parsed_options[:db_exports_list]) opts = default_opts('db_exports').merge(list: ParsedCli.db_exports_list)
output('@info', msg: "Enumerating DB Exports #{enum_detection_message(opts[:mode])}") if user_interaction? output('@info', msg: "Enumerating DB Exports #{enum_detection_message(opts[:mode])}") if user_interaction?
output('db_exports', db_exports: target.db_exports(opts)) output('db_exports', db_exports: target.db_exports(opts))
end end
def enum_medias def enum_medias
opts = default_opts('medias').merge(range: parsed_options[:enumerate][:medias]) opts = default_opts('medias').merge(range: ParsedCli.enumerate[:medias])
if user_interaction? if user_interaction?
output('@info', output('@info',
@@ -181,13 +183,13 @@ module WPScan
# #
# @return [ Boolean ] Wether or not to enumerate the users # @return [ Boolean ] Wether or not to enumerate the users
def enum_users?(opts) def enum_users?(opts)
opts[:users] || (parsed_options[:passwords] && !parsed_options[:username] && !parsed_options[:usernames]) opts[:users] || (ParsedCli.passwords && !ParsedCli.username && !ParsedCli.usernames)
end end
def enum_users def enum_users
opts = default_opts('users').merge( opts = default_opts('users').merge(
range: enum_users_range, range: enum_users_range,
list: parsed_options[:users_list] list: ParsedCli.users_list
) )
output('@info', msg: "Enumerating Users #{enum_detection_message(opts[:mode])}") if user_interaction? output('@info', msg: "Enumerating Users #{enum_detection_message(opts[:mode])}") if user_interaction?
@@ -198,7 +200,7 @@ module WPScan
# If the --enumerate is used, the default value is handled by the Option # If the --enumerate is used, the default value is handled by the Option
# However, when using --passwords alone, the default has to be set by the code below # However, when using --passwords alone, the default has to be set by the code below
def enum_users_range def enum_users_range
parsed_options[:enumerate][:users] || cli_enum_choices[0].choices[:u].validate(nil) ParsedCli.enumerate[:users] || cli_enum_choices[0].choices[:u].validate(nil)
end end
end end
end end

View File

@@ -18,9 +18,9 @@ module WPScan
output( output(
'theme', 'theme',
theme: target.main_theme( theme: target.main_theme(
mode: parsed_options[:main_theme_detection] || parsed_options[:detection_mode] mode: ParsedCli.main_theme_detection || ParsedCli.detection_mode
), ),
verbose: parsed_options[:verbose] verbose: ParsedCli.verbose
) )
end end
end end

View File

@@ -24,7 +24,7 @@ module WPScan
end end
def run def run
return unless parsed_options[:passwords] return unless ParsedCli.passwords
if user_interaction? if user_interaction?
output('@info', output('@info',
@@ -33,13 +33,13 @@ module WPScan
attack_opts = { attack_opts = {
show_progression: user_interaction?, show_progression: user_interaction?,
multicall_max_passwords: parsed_options[:multicall_max_passwords] multicall_max_passwords: ParsedCli.multicall_max_passwords
} }
begin begin
found = [] found = []
attacker.attack(users, passwords(parsed_options[:passwords]), attack_opts) do |user| attacker.attack(users, passwords(ParsedCli.passwords), attack_opts) do |user|
found << user found << user
attacker.progress_bar.log("[SUCCESS] - #{user.username} / #{user.password}") attacker.progress_bar.log("[SUCCESS] - #{user.username} / #{user.password}")
@@ -61,42 +61,55 @@ module WPScan
# @return [ CMSScanner::Finders::Finder ] # @return [ CMSScanner::Finders::Finder ]
def attacker_from_cli_options def attacker_from_cli_options
return unless parsed_options[:password_attack] return unless ParsedCli.password_attack
case parsed_options[:password_attack] case ParsedCli.password_attack
when :wp_login when :wp_login
WPScan::Finders::Passwords::WpLogin.new(target) Finders::Passwords::WpLogin.new(target)
when :xmlrpc when :xmlrpc
raise Error::XMLRPCNotDetected unless xmlrpc raise Error::XMLRPCNotDetected unless xmlrpc
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc) Finders::Passwords::XMLRPC.new(xmlrpc)
when :xmlrpc_multicall when :xmlrpc_multicall
raise Error::XMLRPCNotDetected unless xmlrpc raise Error::XMLRPCNotDetected unless xmlrpc
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc) Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
end
end
# @return [ Boolean ]
def xmlrpc_get_users_blogs_enabled?
if xmlrpc&.enabled? &&
xmlrpc.available_methods.include?('wp.getUsersBlogs') &&
xmlrpc.method_call('wp.getUsersBlogs', [SecureRandom.hex[0, 6], SecureRandom.hex[0, 4]])
.run.body !~ /XML\-RPC services are disabled/
true
else
false
end end
end end
# @return [ CMSScanner::Finders::Finder ] # @return [ CMSScanner::Finders::Finder ]
def attacker_from_automatic_detection def attacker_from_automatic_detection
if xmlrpc&.enabled? && xmlrpc.available_methods.include?('wp.getUsersBlogs') if xmlrpc_get_users_blogs_enabled?
wp_version = target.wp_version wp_version = target.wp_version
if wp_version && wp_version < '4.4' if wp_version && wp_version < '4.4'
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc) Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
else else
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc) Finders::Passwords::XMLRPC.new(xmlrpc)
end end
else else
WPScan::Finders::Passwords::WpLogin.new(target) Finders::Passwords::WpLogin.new(target)
end end
end end
# @return [ Array<Users> ] The users to brute force # @return [ Array<Users> ] The users to brute force
def users def users
return target.users unless parsed_options[:usernames] return target.users unless ParsedCli.usernames
parsed_options[:usernames].reduce([]) do |acc, elem| ParsedCli.usernames.reduce([]) do |acc, elem|
acc << Model::User.new(elem.chomp) acc << Model::User.new(elem.chomp)
end end
end end

View File

@@ -0,0 +1,30 @@
# frozen_string_literal: true
module WPScan
module Controller
# Controller to handle the API token
class VulnApi < CMSScanner::Controller::Base
def cli_options
[
OptString.new(['--api-token TOKEN', 'The WPVulnDB API Token to display vulnerability data'])
]
end
def before_scan
return unless ParsedCli.api_token
DB::VulnApi.token = ParsedCli.api_token
api_status = DB::VulnApi.status
raise Error::InvalidApiToken if api_status['error']
raise Error::ApiLimitReached if api_status['requests_remaining'] == 0
raise api_status['http_error'] if api_status['http_error']
end
def after_scan
output('status', status: DB::VulnApi.status, api_requests: WPScan.api_requests)
end
end
end
end

View File

@@ -17,15 +17,15 @@ module WPScan
end end
def before_scan def before_scan
WPScan::DB::DynamicFinders::Wordpress.create_versions_finders DB::DynamicFinders::Wordpress.create_versions_finders
end end
def run def run
output( output(
'version', 'version',
version: target.wp_version( version: target.wp_version(
mode: parsed_options[:wp_version_detection] || parsed_options[:detection_mode], mode: ParsedCli.wp_version_detection || ParsedCli.detection_mode,
confidence_threshold: parsed_options[:wp_version_all] ? 0 : 100, confidence_threshold: ParsedCli.wp_version_all ? 0 : 100,
show_progression: user_interaction? show_progression: user_interaction?
) )
) )

View File

@@ -20,9 +20,9 @@ module WPScan
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res| enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
if res.effective_url.end_with?('.zip') if res.effective_url.end_with?('.zip')
next unless res.headers['Content-Type'] =~ %r{\Aapplication/zip}i next unless %r{\Aapplication/zip}i.match?(res.headers['Content-Type'])
else else
next unless res.body =~ SQL_PATTERN next unless SQL_PATTERN.match?(res.body)
end end
found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100) found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)

View File

@@ -9,7 +9,7 @@ module WPScan
def aggressive(_opts = {}) def aggressive(_opts = {})
path = 'installer-log.txt' path = 'installer-log.txt'
return unless target.head_and_get(path).body =~ /DUPLICATOR INSTALL-LOG/ return unless /DUPLICATOR INSTALL-LOG/.match?(target.head_and_get(path).body)
Model::DuplicatorInstallerLog.new( Model::DuplicatorInstallerLog.new(
target.url(path), target.url(path),

View File

@@ -14,7 +14,7 @@ module WPScan
Model::EmergencyPwdResetScript.new( Model::EmergencyPwdResetScript.new(
target.url(path), target.url(path),
confidence: res.body =~ /password/i ? 100 : 40, confidence: /password/i.match?(res.body) ? 100 : 40,
found_by: DIRECT_ACCESS, found_by: DIRECT_ACCESS,
references: { references: {
url: 'https://codex.wordpress.org/Resetting_Your_Password#Using_the_Emergency_Password_Reset_Script' url: 'https://codex.wordpress.org/Resetting_Your_Password#Using_the_Emergency_Password_Reset_Script'

View File

@@ -9,11 +9,13 @@ module WPScan
def passive(_opts = {}) def passive(_opts = {})
pattern = %r{#{target.content_dir}/mu\-plugins/}i pattern = %r{#{target.content_dir}/mu\-plugins/}i
target.in_scope_urls(target.homepage_res) do |url| target.in_scope_uris(target.homepage_res) do |uri|
next unless Addressable::URI.parse(url).path =~ pattern next unless uri.path&.match?(pattern)
url = target.url('wp-content/mu-plugins/') url = target.url('wp-content/mu-plugins/')
target.mu_plugins = true
return Model::MuPlugins.new( return Model::MuPlugins.new(
url, url,
confidence: 70, confidence: 70,
@@ -33,8 +35,6 @@ module WPScan
return unless [200, 401, 403].include?(res.code) return unless [200, 401, 403].include?(res.code)
return if target.homepage_or_404?(res) return if target.homepage_or_404?(res)
# TODO: add the check for --exclude-content once implemented ?
target.mu_plugins = true target.mu_plugins = true
Model::MuPlugins.new( Model::MuPlugins.new(

View File

@@ -12,7 +12,7 @@ module WPScan
path = 'wp-content/uploads/dump.sql' path = 'wp-content/uploads/dump.sql'
res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } }) res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } })
return unless res.body =~ SQL_PATTERN return unless SQL_PATTERN.match?(res.body)
Model::UploadSQLDump.new( Model::UploadSQLDump.new(
target.url(path), target.url(path),

View File

@@ -20,10 +20,10 @@ module WPScan
end end
def passive_from_css_href(res, opts) def passive_from_css_href(res, opts)
target.in_scope_urls(res, '//style/@src|//link/@href') do |url| target.in_scope_uris(res, '//style/@src|//link/@href') do |uri|
next unless Addressable::URI.parse(url).path =~ %r{/themes/([^\/]+)/style.css\z}i next unless uri.path =~ %r{/themes/([^\/]+)/style.css\z}i
return create_theme(Regexp.last_match[1], url, opts) return create_theme(Regexp.last_match[1], uri.to_s, opts)
end end
nil nil
end end

View File

@@ -13,7 +13,7 @@ module WPScan
def valid_credentials?(response) def valid_credentials?(response)
response.code == 302 && response.code == 302 &&
response.headers['Set-Cookie']&.any? { |cookie| cookie =~ /wordpress_logged_in_/i } [*response.headers['Set-Cookie']]&.any? { |cookie| cookie =~ /wordpress_logged_in_/i }
end end
def errored_response?(response) def errored_response?(response)

View File

@@ -8,7 +8,7 @@ module WPScan
include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack
def login_request(username, password) def login_request(username, password)
target.method_call('wp.getUsersBlogs', [username, password]) target.method_call('wp.getUsersBlogs', [username, password], cache_ttl: 0)
end end
def valid_credentials?(response) def valid_credentials?(response)

View File

@@ -19,7 +19,7 @@ module WPScan
end end
end end
target.multi_call(methods).run target.multi_call(methods, cache_ttl: 0).run
end end
# @param [ Array<Model::User> ] users # @param [ Array<Model::User> ] users

View File

@@ -13,25 +13,15 @@ module WPScan
def initialize(plugin) def initialize(plugin)
finders << PluginVersion::Readme.new(plugin) finders << PluginVersion::Readme.new(plugin)
load_specific_finders(plugin) create_and_load_dynamic_versions_finders(plugin)
end end
# Load the finders associated with the plugin # Create the dynamic version finders related to the plugin and register them
# #
# @param [ Model::Plugin ] plugin # @param [ Model::Plugin ] plugin
def load_specific_finders(plugin) def create_and_load_dynamic_versions_finders(plugin)
module_name = plugin.classify DB::DynamicFinders::Plugin.create_versions_finders(plugin.slug).each do |finder|
finders << finder.new(plugin)
return unless Finders::PluginVersion.constants.include?(module_name)
mod = Finders::PluginVersion.const_get(module_name)
mod.constants.each do |constant|
c = mod.const_get(constant)
next unless c.is_a?(Class)
finders << c.new(plugin)
end end
end end
end end

View File

@@ -11,7 +11,7 @@ module WPScan
# The target(plugin)#readme_url can't be used directly here # The target(plugin)#readme_url can't be used directly here
# as if the --detection-mode is passive, it will always return nil # as if the --detection-mode is passive, it will always return nil
Model::WpItem::READMES.each do |file| target.potential_readme_filenames.each do |file|
res = target.head_and_get(file) res = target.head_and_get(file)
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty? next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
@@ -52,7 +52,7 @@ module WPScan
number = Regexp.last_match[1] number = Regexp.last_match[1]
number if number =~ /[0-9]+/ number if /[0-9]+/.match?(number)
end end
# @param [ String ] body # @param [ String ] body

View File

@@ -4,7 +4,7 @@ module WPScan
module Finders module Finders
module Plugins module Plugins
# Plugins finder from Dynamic Finder 'BodyPattern' # Plugins finder from Dynamic Finder 'BodyPattern'
class BodyPattern < WPScan::Finders::DynamicFinder::WpItems::Finder class BodyPattern < Finders::DynamicFinder::WpItems::Finder
DEFAULT_CONFIDENCE = 30 DEFAULT_CONFIDENCE = 30
# @param [ Hash ] opts The options from the #passive, #aggressive methods # @param [ Hash ] opts The options from the #passive, #aggressive methods
@@ -15,7 +15,7 @@ module WPScan
# #
# @return [ Plugin ] The detected plugin in the response, related to the config # @return [ Plugin ] The detected plugin in the response, related to the config
def process_response(opts, response, slug, klass, config) def process_response(opts, response, slug, klass, config)
return unless response.body =~ config['pattern'] return unless response.body&.match?(config['pattern'])
Model::Plugin.new( Model::Plugin.new(
slug, slug,

View File

@@ -4,7 +4,7 @@ module WPScan
module Finders module Finders
module Plugins module Plugins
# Plugins finder from the Dynamic Finder 'Comment' # Plugins finder from the Dynamic Finder 'Comment'
class Comment < WPScan::Finders::DynamicFinder::WpItems::Finder class Comment < Finders::DynamicFinder::WpItems::Finder
DEFAULT_CONFIDENCE = 30 DEFAULT_CONFIDENCE = 30
# @param [ Hash ] opts The options from the #passive, #aggressive methods # @param [ Hash ] opts The options from the #passive, #aggressive methods
@@ -18,7 +18,7 @@ module WPScan
response.html.xpath(config['xpath'] || '//comment()').each do |node| response.html.xpath(config['xpath'] || '//comment()').each do |node|
comment = node.text.to_s.strip comment = node.text.to_s.strip
next unless comment =~ config['pattern'] next unless comment&.match?(config['pattern'])
return Model::Plugin.new( return Model::Plugin.new(
slug, slug,

View File

@@ -4,7 +4,7 @@ module WPScan
module Finders module Finders
module Plugins module Plugins
# Plugins finder from Dynamic Finder 'ConfigParser' # Plugins finder from Dynamic Finder 'ConfigParser'
class ConfigParser < WPScan::Finders::DynamicFinder::WpItems::Finder class ConfigParser < Finders::DynamicFinder::WpItems::Finder
DEFAULT_CONFIDENCE = 40 DEFAULT_CONFIDENCE = 40
# @param [ Hash ] opts The options from the #passive, #aggressive methods # @param [ Hash ] opts The options from the #passive, #aggressive methods

View File

@@ -4,7 +4,7 @@ module WPScan
module Finders module Finders
module Plugins module Plugins
# Plugins finder from Dynamic Finder 'HeaderPattern' # Plugins finder from Dynamic Finder 'HeaderPattern'
class HeaderPattern < WPScan::Finders::DynamicFinder::WpItems::Finder class HeaderPattern < Finders::DynamicFinder::WpItems::Finder
DEFAULT_CONFIDENCE = 30 DEFAULT_CONFIDENCE = 30
# @param [ Hash ] opts # @param [ Hash ] opts

View File

@@ -4,7 +4,7 @@ module WPScan
module Finders module Finders
module Plugins module Plugins
# Plugins finder from the Dynamic Finder 'JavascriptVar' # Plugins finder from the Dynamic Finder 'JavascriptVar'
class JavascriptVar < WPScan::Finders::DynamicFinder::WpItems::Finder class JavascriptVar < Finders::DynamicFinder::WpItems::Finder
DEFAULT_CONFIDENCE = 60 DEFAULT_CONFIDENCE = 60
# @param [ Hash ] opts The options from the #passive, #aggressive methods # @param [ Hash ] opts The options from the #passive, #aggressive methods

View File

@@ -9,7 +9,7 @@ module WPScan
# @return [ Array<Integer> ] # @return [ Array<Integer> ]
def valid_response_codes def valid_response_codes
@valid_response_codes ||= [200, 401, 403, 301, 500].freeze @valid_response_codes ||= [200, 401, 403, 500].freeze
end end
# @param [ Hash ] opts # @param [ Hash ] opts
@@ -19,8 +19,10 @@ module WPScan
def aggressive(opts = {}) def aggressive(opts = {})
found = [] found = []
enumerate(target_urls(opts), opts.merge(check_full_response: [200, 401, 403, 500])) do |_res, slug| enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80)) found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
raise Error::PluginsThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
end end
found found

View File

@@ -4,7 +4,7 @@ module WPScan
module Finders module Finders
module Plugins module Plugins
# Plugins finder from Dynamic Finder 'QueryParameter' # Plugins finder from Dynamic Finder 'QueryParameter'
class QueryParameter < WPScan::Finders::DynamicFinder::WpItems::Finder class QueryParameter < Finders::DynamicFinder::WpItems::Finder
DEFAULT_CONFIDENCE = 10 DEFAULT_CONFIDENCE = 10
def passive(_opts = {}) def passive(_opts = {})

View File

@@ -4,7 +4,7 @@ module WPScan
module Finders module Finders
module Plugins module Plugins
# Plugins finder from the Dynamic Finder 'Xpath' # Plugins finder from the Dynamic Finder 'Xpath'
class Xpath < WPScan::Finders::DynamicFinder::WpItems::Finder class Xpath < Finders::DynamicFinder::WpItems::Finder
DEFAULT_CONFIDENCE = 40 DEFAULT_CONFIDENCE = 40
# @param [ Hash ] opts The options from the #passive, #aggressive methods # @param [ Hash ] opts The options from the #passive, #aggressive methods

View File

@@ -16,25 +16,15 @@ module WPScan
ThemeVersion::Style.new(theme) << ThemeVersion::Style.new(theme) <<
ThemeVersion::WooFrameworkMetaGenerator.new(theme) ThemeVersion::WooFrameworkMetaGenerator.new(theme)
load_specific_finders(theme) create_and_load_dynamic_versions_finders(theme)
end end
# Load the finders associated with the theme # Create the dynamic version finders related to the theme and register them
# #
# @param [ Model::Theme ] theme # @param [ Model::Theme ] theme
def load_specific_finders(theme) def create_and_load_dynamic_versions_finders(theme)
module_name = theme.classify DB::DynamicFinders::Theme.create_versions_finders(theme.slug).each do |finder|
finders << finder.new(theme)
return unless Finders::ThemeVersion.constants.include?(module_name)
mod = Finders::ThemeVersion.const_get(module_name)
mod.constants.each do |constant|
c = mod.const_get(constant)
next unless c.is_a?(Class)
finders << c.new(theme)
end end
end end
end end

View File

@@ -9,7 +9,7 @@ module WPScan
# @return [ Array<Integer> ] # @return [ Array<Integer> ]
def valid_response_codes def valid_response_codes
@valid_response_codes ||= [200, 401, 403, 301, 500].freeze @valid_response_codes ||= [200, 401, 403, 500].freeze
end end
# @param [ Hash ] opts # @param [ Hash ] opts
@@ -19,8 +19,10 @@ module WPScan
def aggressive(opts = {}) def aggressive(opts = {})
found = [] found = []
enumerate(target_urls(opts), opts.merge(check_full_response: [200, 401, 403, 500])) do |_res, slug| enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80)) found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
raise Error::ThemesThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
end end
found found

View File

@@ -22,7 +22,7 @@ module WPScan
found = [] found = []
enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res| enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res|
next unless res.body =~ /no image specified/i next unless /no image specified/i.match?(res.body)
found << Model::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100)) found << Model::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
end end

View File

@@ -7,6 +7,11 @@ module WPScan
class AuthorIdBruteForcing < CMSScanner::Finders::Finder class AuthorIdBruteForcing < CMSScanner::Finders::Finder
include CMSScanner::Finders::Finder::Enumerator include CMSScanner::Finders::Finder::Enumerator
# @return [ Array<Integer> ]
def valid_response_codes
@valid_response_codes ||= [200, 301, 302]
end
# @param [ Hash ] opts # @param [ Hash ] opts
# @option opts [ Range ] :range Mandatory # @option opts [ Range ] :range Mandatory
# #
@@ -15,7 +20,7 @@ module WPScan
found = [] found = []
found_by_msg = 'Author Id Brute Forcing - %s (Aggressive Detection)' found_by_msg = 'Author Id Brute Forcing - %s (Aggressive Detection)'
enumerate(target_urls(opts), opts) do |res, id| enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, id|
username, found_by, confidence = potential_username(res) username, found_by, confidence = potential_username(res)
next unless username next unless username
@@ -49,7 +54,7 @@ module WPScan
super(opts.merge(title: ' Brute Forcing Author IDs -')) super(opts.merge(title: ' Brute Forcing Author IDs -'))
end end
def request_params def full_request_params
{ followlocation: true } { followlocation: true }
end end
@@ -78,8 +83,8 @@ module WPScan
# @return [ String, nil ] The username found # @return [ String, nil ] The username found
def username_from_response(res) def username_from_response(res)
# Permalink enabled # Permalink enabled
target.in_scope_urls(res, '//link/@href|//a/@href') do |url| target.in_scope_uris(res, '//link/@href|//a/@href') do |uri|
username = username_from_author_url(url) username = username_from_author_url(uri.to_s)
return username if username return username if username
end end

View File

@@ -45,12 +45,10 @@ module WPScan
def potential_usernames(res) def potential_usernames(res)
usernames = [] usernames = []
target.in_scope_urls(res, '//a/@href') do |url, node| target.in_scope_uris(res, '//a/@href') do |uri, node|
uri = Addressable::URI.parse(url)
if uri.path =~ %r{/author/([^/\b]+)/?\z}i if uri.path =~ %r{/author/([^/\b]+)/?\z}i
usernames << [Regexp.last_match[1], 'Author Pattern', 100] usernames << [Regexp.last_match[1], 'Author Pattern', 100]
elsif uri.query =~ /author=[0-9]+/ elsif /author=[0-9]+/.match?(uri.query)
usernames << [node.text.to_s.strip, 'Display Name', 30] usernames << [node.text.to_s.strip, 'Display Name', 30]
end end
end end

View File

@@ -24,7 +24,7 @@ module WPScan
return found if error.empty? # Protection plugin / error disabled return found if error.empty? # Protection plugin / error disabled
next unless error =~ /The password you entered for the username|Incorrect Password/i next unless /The password you entered for the username|Incorrect Password/i.match?(error)
found << Model::User.new(username, found_by: found_by, confidence: 100) found << Model::User.new(username, found_by: found_by, confidence: 100)
end end

View File

@@ -6,7 +6,7 @@ module WPScan
# Users disclosed from the dc:creator field in the RSS # Users disclosed from the dc:creator field in the RSS
# The names disclosed are display names, however depending on the configuration of the blog, # The names disclosed are display names, however depending on the configuration of the blog,
# they can be the same than usernames # they can be the same than usernames
class RSSGenerator < WPScan::Finders::WpVersion::RSSGenerator class RSSGenerator < Finders::WpVersion::RSSGenerator
def process_urls(urls, _opts = {}) def process_urls(urls, _opts = {})
found = [] found = []

View File

@@ -57,9 +57,7 @@ module WPScan
def api_url def api_url
return @api_url if @api_url return @api_url if @api_url
target.in_scope_urls(target.homepage_res, "//link[@rel='https://api.w.org/']/@href").each do |url, _tag| target.in_scope_uris(target.homepage_res, "//link[@rel='https://api.w.org/']/@href").each do |uri|
uri = Addressable::URI.parse(url.strip)
return @api_url = uri.join('wp/v2/users/').to_s if uri.path.include?('wp-json') return @api_url = uri.join('wp/v2/users/').to_s if uri.path.include?('wp-json')
end end

View File

@@ -12,8 +12,8 @@ module WPScan
def items_from_links(type, uniq = true) def items_from_links(type, uniq = true)
found = [] found = []
target.in_scope_urls(target.homepage_res) do |url| target.in_scope_uris(target.homepage_res) do |uri|
next unless url =~ item_attribute_pattern(type) next unless uri.to_s =~ item_attribute_pattern(type)
found << Regexp.last_match[1] found << Regexp.last_match[1]
end end

View File

@@ -28,7 +28,7 @@ module WPScan
# @param [ WPScan::Target ] target # @param [ WPScan::Target ] target
def initialize(target) def initialize(target)
(%w[RSSGenerator AtomGenerator RDFGenerator] + (%w[RSSGenerator AtomGenerator RDFGenerator] +
WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.keys + DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
%w[Readme UniqueFingerprinting] %w[Readme UniqueFingerprinting]
).each do |finder_name| ).each do |finder_name|
finders << WpVersion.const_get(finder_name.to_sym).new(target) finders << WpVersion.const_get(finder_name.to_sym).new(target)

View File

@@ -15,9 +15,16 @@ module WPScan
@uri = Addressable::URI.parse(blog.url(path_from_blog)) @uri = Addressable::URI.parse(blog.url(path_from_blog))
end end
# @return [ JSON ] # Retrieve the metadata from the vuln API if available (and a valid token is given),
# or the local metadata db otherwise
# @return [ Hash ]
def metadata
@metadata ||= db_data.empty? ? DB::Plugin.metadata_at(slug) : db_data
end
# @return [ Hash ]
def db_data def db_data
@db_data ||= DB::Plugin.db_data(slug) @db_data ||= DB::VulnApi.plugin_data(slug)
end end
# @param [ Hash ] opts # @param [ Hash ] opts
@@ -28,6 +35,11 @@ module WPScan
@version @version
end end
# @return [ Array<String> ]
def potential_readme_filenames
@potential_readme_filenames ||= [*(DB::DynamicFinders::Plugin.df_data.dig(slug, 'Readme', 'path') || super)]
end
end end
end end
end end

View File

@@ -21,9 +21,16 @@ module WPScan
parse_style parse_style
end end
# Retrieve the metadata from the vuln API if available (and a valid token is given),
# or the local metadata db otherwise
# @return [ JSON ] # @return [ JSON ]
def metadata
@metadata ||= db_data.empty? ? DB::Theme.metadata_at(slug) : db_data
end
# @return [ Hash ]
def db_data def db_data
@db_data ||= DB::Theme.db_data(slug) @db_data ||= DB::VulnApi.theme_data(slug)
end end
# @param [ Hash ] opts # @param [ Hash ] opts

View File

@@ -63,7 +63,7 @@ module WPScan
def webshot_enabled? def webshot_enabled?
res = Browser.get(url, params: { webshot: 1, src: "http://#{default_allowed_domains.sample}" }) res = Browser.get(url, params: { webshot: 1, src: "http://#{default_allowed_domains.sample}" })
res.body =~ /WEBSHOT_ENABLED == true/ ? false : true /WEBSHOT_ENABLED == true/.match?(res.body) ? false : 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

@@ -9,11 +9,12 @@ module WPScan
include CMSScanner::Target::Platform::PHP include CMSScanner::Target::Platform::PHP
include CMSScanner::Target::Server::Generic include CMSScanner::Target::Server::Generic
# Most common readme filenames, based on checking all public plugins and themes.
READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
delegate :homepage_res, :xpath_pattern_from_page, :in_scope_urls, :head_or_get_params, to: :blog delegate :homepage_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
# @param [ String ] slug The plugin/theme slug # @param [ String ] slug The plugin/theme slug
# @param [ Target ] blog The targeted blog # @param [ Target ] blog The targeted blog
@@ -59,18 +60,18 @@ module WPScan
# @return [ String ] # @return [ String ]
def latest_version def latest_version
@latest_version ||= db_data['latest_version'] ? Model::Version.new(db_data['latest_version']) : nil @latest_version ||= metadata['latest_version'] ? Model::Version.new(metadata['latest_version']) : nil
end end
# Not used anywhere ATM # Not used anywhere ATM
# @return [ Boolean ] # @return [ Boolean ]
def popular? def popular?
@popular ||= db_data['popular'] @popular ||= metadata['popular'] ? true : false
end end
# @return [ String ] # @return [ String ]
def last_updated def last_updated
@last_updated ||= db_data['last_updated'] @last_updated ||= metadata['last_updated']
end end
# @return [ Boolean ] # @return [ Boolean ]
@@ -117,7 +118,7 @@ module WPScan
return @readme_url unless @readme_url.nil? return @readme_url unless @readme_url.nil?
READMES.each do |path| potential_readme_filenames.each do |path|
t_url = url(path) t_url = url(path)
return @readme_url = t_url if Browser.forge_request(t_url, blog.head_or_get_params).run.code == 200 return @readme_url = t_url if Browser.forge_request(t_url, blog.head_or_get_params).run.code == 200
@@ -126,6 +127,10 @@ module WPScan
@readme_url = false @readme_url = false
end end
def potential_readme_filenames
@potential_readme_filenames ||= READMES
end
# @param [ String ] path # @param [ String ] path
# @param [ Hash ] params The request params # @param [ Hash ] params The request params
# #

View File

@@ -35,9 +35,16 @@ module WPScan
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) } @all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
end end
# @return [ JSON ] # Retrieve the metadata from the vuln API if available (and a valid token is given),
# or the local metadata db otherwise
# @return [ Hash ]
def metadata
@metadata ||= db_data.empty? ? DB::Version.metadata_at(number) : db_data
end
# @return [ Hash ]
def db_data def db_data
@db_data ||= DB::Version.db_data(number) @db_data ||= DB::VulnApi.wordpress_data(number)
end end
# @return [ Array<Vulnerability> ] # @return [ Array<Vulnerability> ]
@@ -55,12 +62,12 @@ module WPScan
# @return [ String ] # @return [ String ]
def release_date def release_date
@release_date ||= db_data['release_date'] || 'Unknown' @release_date ||= metadata['release_date'] || 'Unknown'
end end
# @return [ String ] # @return [ String ]
def status def status
@status ||= db_data['status'] || 'Unknown' @status ||= metadata['status'] || 'Unknown'
end end
end end
end end

View File

@@ -8,7 +8,7 @@ _______________________________________________________________
WordPress Security Scanner by the WPScan Team WordPress Security Scanner by the WPScan Team
Version <%= WPScan::VERSION %> Version <%= WPScan::VERSION %>
Sponsored by Sucuri - https://sucuri.net <%= ' ' * ((63 - WPScan::DB::Sponsor.text.length)/2) + WPScan::DB::Sponsor.text %>
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_ @_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
_______________________________________________________________ _______________________________________________________________

View File

@@ -5,7 +5,7 @@
<%= notice_icon %> Config Backup(s) Identified: <%= notice_icon %> Config Backup(s) Identified:
<% @config_backups.each do |config_backup| -%> <% @config_backups.each do |config_backup| -%>
<%= info_icon %> <%= config_backup %> <%= critical_icon %> <%= config_backup %>
<%= render('@finding', item: config_backup) -%> <%= render('@finding', item: config_backup) -%>
<% end -%> <% end -%>
<% end %> <% end %>

View File

@@ -5,7 +5,7 @@
<%= notice_icon %> Db Export(s) Identified: <%= notice_icon %> Db Export(s) Identified:
<% @db_exports.each do |db_export| -%> <% @db_exports.each do |db_export| -%>
<%= info_icon %> <%= db_export %> <%= critical_icon %> <%= db_export %>
<%= render('@finding', item: db_export) -%> <%= render('@finding', item: db_export) -%>
<% end -%> <% end -%>
<% end %> <% end %>

View File

@@ -0,0 +1,13 @@
<% unless @status.empty? -%>
<% if @status['http_error'] -%>
<%= critical_icon %> WPVulnDB API, <%= @status['http_error'].to_s %>
<% else -%>
<%= info_icon %> WPVulnDB API OK
| Plan: <%= @status['plan'] %>
| Requests Done (during the scan): <%= @api_requests %>
| Requests Remaining: <%= @status['requests_remaining'] %>
<% end -%>
<% else -%>
<%= warning_icon %> No WPVulnDB API Token given, as a result vulnerability data has not been output.
<%= warning_icon %> You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/register.
<% end -%>

View File

@@ -7,5 +7,5 @@
"@erwan_lr", "@erwan_lr",
"@_FireFart_" "@_FireFart_"
], ],
"sponsored_by": "Sucuri - https://sucuri.net" "sponsor": <%= WPScan::DB::Sponsor.text.to_json %>
}, },

View File

@@ -11,9 +11,10 @@
}<% unless index == last_index %>,<% end -%> }<% unless index == last_index %>,<% end -%>
<% end -%> <% end -%>
<% end -%> <% end -%>
}, }
"vulnerabilities": [ <% if @item.respond_to?(:vulnerabilities) -%>
<% if @item.respond_to?(:vulnerabilities) && !(vulns = @item.vulnerabilities).empty? -%> ,"vulnerabilities": [
<% unless (vulns = @item.vulnerabilities).empty? -%>
<% last_index = vulns.size - 1 -%> <% last_index = vulns.size - 1 -%>
<% vulns.each_with_index do |v, index| -%> <% vulns.each_with_index do |v, index| -%>
{ {
@@ -23,4 +24,5 @@
}<% unless index == last_index -%>,<% end -%> }<% unless index == last_index -%>,<% end -%>
<% end -%> <% end -%>
<% end -%> <% end -%>
] ]
<% end -%>

View File

@@ -0,0 +1,13 @@
"vuln_api": {
<% unless @status.empty? -%>
<% if @status['http_error'] -%>
"http_error": <%= @status['http_error'].to_s.to_json %>
<% else -%>
"plan": <%= @status['plan'].to_json %>,
"requests_done_during_scan": <%= @api_requests.to_json %>,
"requests_remaining": <%= @status['requests_remaining'].to_json %>
<% end -%>
<% else -%>
"error": "No WPVulnDB API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpvulndb.com/register."
<% end -%>
},

View File

@@ -5,6 +5,7 @@ require 'wpscan'
WPScan::Scan.new do |s| WPScan::Scan.new do |s|
s.controllers << s.controllers <<
WPScan::Controller::VulnApi.new <<
WPScan::Controller::CustomDirectories.new << WPScan::Controller::CustomDirectories.new <<
WPScan::Controller::InterestingFindings.new << WPScan::Controller::InterestingFindings.new <<
WPScan::Controller::WpVersion.new << WPScan::Controller::WpVersion.new <<

View File

@@ -7,6 +7,7 @@ require 'wpscan'
report = MemoryProfiler.report(top: 15) do report = MemoryProfiler.report(top: 15) do
WPScan::Scan.new do |s| WPScan::Scan.new do |s|
s.controllers << s.controllers <<
WPScan::Controller::VulnApi.new <<
WPScan::Controller::CustomDirectories.new << WPScan::Controller::CustomDirectories.new <<
WPScan::Controller::InterestingFindings.new << WPScan::Controller::InterestingFindings.new <<
WPScan::Controller::WpVersion.new << WPScan::Controller::WpVersion.new <<

View File

@@ -12,6 +12,7 @@ StackProf.run(mode: :cpu, out: '/tmp/stackprof-cpu.dump', interval: 500) do
# require_relative 'wpscan' doesn't work # require_relative 'wpscan' doesn't work
WPScan::Scan.new do |s| WPScan::Scan.new do |s|
s.controllers << s.controllers <<
WPScan::Controller::VulnApi.new <<
WPScan::Controller::CustomDirectories.new << WPScan::Controller::CustomDirectories.new <<
WPScan::Controller::InterestingFindings.new << WPScan::Controller::InterestingFindings.new <<
WPScan::Controller::WpVersion.new << WPScan::Controller::WpVersion.new <<

View File

@@ -13,12 +13,14 @@ require 'uri'
require 'time' require 'time'
require 'readline' require 'readline'
require 'securerandom' require 'securerandom'
# Monkey Patches/Fixes/Override
require 'wpscan/typhoeus/response' # Adds a from_vuln_api? method
# Custom Libs # Custom Libs
require 'wpscan/helper' require 'wpscan/helper'
require 'wpscan/db' require 'wpscan/db'
require 'wpscan/version' require 'wpscan/version'
require 'wpscan/errors' require 'wpscan/errors'
require 'wpscan/parsed_cli'
require 'wpscan/browser' require 'wpscan/browser'
require 'wpscan/target' require 'wpscan/target'
require 'wpscan/finders' require 'wpscan/finders'
@@ -37,12 +39,28 @@ module WPScan
APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path
DB_DIR = Pathname.new(Dir.home).join('.wpscan', 'db') DB_DIR = Pathname.new(Dir.home).join('.wpscan', 'db')
Typhoeus.on_complete do |response|
next if response.cached? || !response.from_vuln_api?
self.api_requests += 1
end
# Override, otherwise it would be returned as 'wp_scan' # Override, otherwise it would be returned as 'wp_scan'
# #
# @return [ String ] # @return [ String ]
def self.app_name def self.app_name
'wpscan' 'wpscan'
end end
# @return [ Integer ]
def self.api_requests
@@api_requests ||= 0
end
# @param [ Integer ] value
def self.api_requests=(value)
@@api_requests = value
end
end end
require "#{WPScan::APP_DIR}/app" require "#{WPScan::APP_DIR}/app"

View File

@@ -5,14 +5,9 @@ module WPScan
class Browser < CMSScanner::Browser class Browser < CMSScanner::Browser
extend Actions extend Actions
# @return [ String ] The path to the user agents list
def user_agents_list
@user_agents_list ||= DB_DIR.join('user-agents.txt').to_s
end
# @return [ String ] # @return [ String ]
def default_user_agent def default_user_agent
"WPScan v#{VERSION} (https://wpscan.org/)" @default_user_agent ||= "WPScan v#{VERSION} (https://wpscan.org/)"
end end
end end
end end

View File

@@ -7,9 +7,12 @@ require_relative 'db/plugins'
require_relative 'db/themes' require_relative 'db/themes'
require_relative 'db/plugin' require_relative 'db/plugin'
require_relative 'db/theme' require_relative 'db/theme'
require_relative 'db/sponsor'
require_relative 'db/wp_version' require_relative 'db/wp_version'
require_relative 'db/fingerprints' require_relative 'db/fingerprints'
require_relative 'db/vuln_api'
require_relative 'db/dynamic_finders/base' require_relative 'db/dynamic_finders/base'
require_relative 'db/dynamic_finders/plugin' require_relative 'db/dynamic_finders/plugin'
require_relative 'db/dynamic_finders/theme' require_relative 'db/dynamic_finders/theme'

View File

@@ -5,18 +5,19 @@ module WPScan
module DynamicFinders module DynamicFinders
class Base class Base
# @return [ String ] # @return [ String ]
def self.db_file def self.df_file
@db_file ||= DB_DIR.join('dynamic_finders.yml').to_s @df_file ||= DB_DIR.join('dynamic_finders.yml').to_s
end end
# @return [ Hash ] # @return [ Hash ]
def self.db_data def self.all_df_data
# true allows aliases to be loaded @all_df_data ||= YAML.safe_load(File.read(df_file), [Regexp])
@db_data ||= YAML.safe_load(File.read(db_file), [Regexp], [], true)
end end
# @return [ Array<Symbol> ] # @return [ Array<Symbol> ]
def self.allowed_classes def self.allowed_classes
# The Readme is not put in there as it's not a Real DF, but rather using the DF system
# to get the list of potential filenames for a given slug
@allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser] @allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser]
end end

View File

@@ -5,8 +5,8 @@ module WPScan
module DynamicFinders module DynamicFinders
class Plugin < Base class Plugin < Base
# @return [ Hash ] # @return [ Hash ]
def self.db_data def self.df_data
@db_data ||= super['plugins'] || {} @df_data ||= all_df_data['plugins'] || {}
end end
def self.version_finder_module def self.version_finder_module
@@ -21,7 +21,7 @@ module WPScan
return configs unless allowed_classes.include?(finder_class) return configs unless allowed_classes.include?(finder_class)
db_data.each do |slug, finders| df_data.each do |slug, finders|
# Quite sure better can be done with some kind of logic statement in the select # Quite sure better can be done with some kind of logic statement in the select
fs = if aggressive fs = if aggressive
finders.reject { |_f, c| c['path'].nil? } finders.reject { |_f, c| c['path'].nil? }
@@ -48,7 +48,7 @@ module WPScan
@versions_finders_configs = {} @versions_finders_configs = {}
db_data.each do |slug, finders| df_data.each do |slug, finders|
finders.each do |finder_name, config| finders.each do |finder_name, config|
next unless config.key?('version') next unless config.key?('version')
@@ -73,23 +73,33 @@ module WPScan
version_finder_module.const_get(constant_name) version_finder_module.const_get(constant_name)
end end
def self.create_versions_finders # Create the dynamic finders related to the given slug, and return the created classes
versions_finders_configs.each do |slug, finders| #
mod = maybe_create_module(slug) # @param [ String ] slug
#
# @return [ Array<Class> ] The created classes
def self.create_versions_finders(slug)
created = []
mod = maybe_create_module(slug)
finders.each do |finder_class, config| versions_finders_configs[slug]&.each do |finder_class, config|
klass = config['class'] || finder_class klass = config['class'] || finder_class
# Instead of raising exceptions, skip unallowed/already defined finders # Instead of raising exceptions, skip unallowed/already defined finders
# So that, when new DF configs are put in the .yml # So that, when new DF configs are put in the .yml
# users with old version of WPScan will still be able to scan blogs # users with old version of WPScan will still be able to scan blogs
# when updating the DB but not the tool # when updating the DB but not the tool
next if mod.constants.include?(finder_class.to_sym) ||
!allowed_classes.include?(klass.to_sym)
version_finder_super_class(klass).create_child_class(mod, finder_class.to_sym, config) next unless allowed_classes.include?(klass.to_sym)
end
created << if mod.constants.include?(finder_class.to_sym)
mod.const_get(finder_class.to_sym)
else
version_finder_super_class(klass).create_child_class(mod, finder_class.to_sym, config)
end
end end
created
end end
# The idea here would be to check if the class exist in # The idea here would be to check if the class exist in

View File

@@ -5,8 +5,8 @@ module WPScan
module DynamicFinders module DynamicFinders
class Theme < Plugin class Theme < Plugin
# @return [ Hash ] # @return [ Hash ]
def self.db_data def self.df_data
@db_data ||= super['themes'] || {} @df_data ||= all_df_data['themes'] || {}
end end
def self.version_finder_module def self.version_finder_module

View File

@@ -5,8 +5,8 @@ module WPScan
module DynamicFinders module DynamicFinders
class Wordpress < Base class Wordpress < Base
# @return [ Hash ] # @return [ Hash ]
def self.db_data def self.df_data
@db_data ||= super['wordpress'] || {} @df_data ||= all_df_data['wordpress'] || {}
end end
# @return [ Constant ] # @return [ Constant ]
@@ -30,9 +30,9 @@ module WPScan
return configs unless allowed_classes.include?(finder_class) return configs unless allowed_classes.include?(finder_class)
finders = if aggressive finders = if aggressive
db_data.reject { |_f, c| c['path'].nil? } df_data.reject { |_f, c| c['path'].nil? }
else else
db_data.select { |_f, c| c['path'].nil? } df_data.select { |_f, c| c['path'].nil? }
end end
finders.each do |finder_name, config| finders.each do |finder_name, config|
@@ -48,7 +48,7 @@ module WPScan
# @return [ Hash ] # @return [ Hash ]
def self.versions_finders_configs def self.versions_finders_configs
@versions_finders_configs ||= db_data.select { |_finder_name, config| config.key?('version') } @versions_finders_configs ||= df_data.select { |_finder_name, config| config.key?('version') }
end end
def self.create_versions_finders def self.create_versions_finders

View File

@@ -4,9 +4,9 @@ module WPScan
module DB module DB
# Plugin DB # Plugin DB
class Plugin < WpItem class Plugin < WpItem
# @return [ String ] # @return [ Hash ]
def self.db_file def self.metadata
@db_file ||= DB_DIR.join('plugins.json').to_s @metadata ||= super['plugins'] || {}
end end
end end
end end

View File

@@ -5,8 +5,8 @@ module WPScan
# WP Plugins # WP Plugins
class Plugins < WpItems class Plugins < WpItems
# @return [ JSON ] # @return [ JSON ]
def self.db def self.metadata
Plugin.db Plugin.metadata
end end
end end
end end

16
lib/wpscan/db/sponsor.rb Normal file
View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
module WPScan
module DB
class Sponsor
# @return [ Hash ]
def self.text
@text ||= file_path.exist? ? File.read(file_path).chomp : ''
end
def self.file_path
@file_path ||= DB_DIR.join('sponsor.txt')
end
end
end
end

View File

@@ -4,9 +4,9 @@ module WPScan
module DB module DB
# Theme DB # Theme DB
class Theme < WpItem class Theme < WpItem
# @return [ String ] # @return [ Hash ]
def self.db_file def self.metadata
@db_file ||= DB_DIR.join('themes.json').to_s @metadata ||= super['themes'] || {}
end end
end end
end end

View File

@@ -5,8 +5,8 @@ module WPScan
# WP Themes # WP Themes
class Themes < WpItems class Themes < WpItems
# @return [ JSON ] # @return [ JSON ]
def self.db def self.metadata
Theme.db Theme.metadata
end end
end end
end end

View File

@@ -7,12 +7,15 @@ module WPScan
class Updater class Updater
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here # /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
FILES = %w[ FILES = %w[
plugins.json themes.json wordpresses.json metadata.json wp_fingerprints.json
timthumbs-v3.txt user-agents.txt config_backups.txt timthumbs-v3.txt config_backups.txt db_exports.txt
db_exports.txt dynamic_finders.yml wp_fingerprints.json LICENSE dynamic_finders.yml LICENSE sponsor.txt
].freeze ].freeze
OLD_FILES = %w[wordpress.db dynamic_finders_01.yml].freeze OLD_FILES = %w[
wordpress.db user-agents.txt dynamic_finders_01.yml
wordpresses.json plugins.json themes.json
].freeze
attr_reader :repo_directory attr_reader :repo_directory
@@ -64,11 +67,12 @@ module WPScan
# @return [ Hash ] The params for Typhoeus::Request # @return [ Hash ] The params for Typhoeus::Request
# @note Those params can't be overriden by CLI options # @note Those params can't be overriden by CLI options
def request_params def request_params
{ @request_params ||= {
timeout: 600, timeout: 600,
connecttimeout: 300, connecttimeout: 300,
accept_encoding: 'gzip, deflate', accept_encoding: 'gzip, deflate',
cache_ttl: 0 cache_ttl: 0,
headers: { 'User-Agent' => Browser.instance.default_user_agent, 'Referer' => nil }
} }
end end

79
lib/wpscan/db/vuln_api.rb Normal file
View File

@@ -0,0 +1,79 @@
# frozen_string_literal: true
module WPScan
module DB
# WPVulnDB API
class VulnApi
NON_ERROR_CODES = [200, 401].freeze
class << self
attr_accessor :token
end
# @return [ Addressable::URI ]
def self.uri
@uri ||= Addressable::URI.parse('https://wpvulndb.com/api/v3/')
end
# @param [ String ] path
# @param [ Hash ] params
#
# @return [ Hash ]
def self.get(path, params = {})
return {} unless token
res = Browser.get(uri.join(path), params.merge(request_params))
return {} if res.code == 404 # This is for API inconsistencies when dots in path
return JSON.parse(res.body) if NON_ERROR_CODES.include?(res.code)
raise Error::HTTP, res
rescue Error::HTTP => e
retries ||= 0
if (retries += 1) <= 3
sleep(1)
retry
end
{ 'http_error' => e }
end
# @return [ Hash ]
def self.plugin_data(slug)
get("plugins/#{slug}")&.dig(slug) || {}
end
# @return [ Hash ]
def self.theme_data(slug)
get("themes/#{slug}")&.dig(slug) || {}
end
# @return [ Hash ]
def self.wordpress_data(version_number)
get("wordpresses/#{version_number.tr('.', '')}")&.dig(version_number) || {}
end
# @return [ Hash ]
def self.status
json = get('status', params: { version: WPScan::VERSION }, cache_ttl: 0)
json['requests_remaining'] = 'Unlimited' if json['requests_remaining'] == -1
json
end
# @return [ Hash ]
def self.request_params
{
headers: {
'Host' => uri.host, # Reset in case user provided a --vhost for the target
'Referer' => nil, # Removes referer set by the cmsscanner to the target url
'User-Agent' => Browser.instance.default_user_agent,
'Authorization' => "Token token=#{token}"
}
}
end
end
end
end

View File

@@ -6,14 +6,19 @@ module WPScan
class WpItem class WpItem
# @param [ String ] identifier The plugin/theme slug or version number # @param [ String ] identifier The plugin/theme slug or version number
# #
# @return [ Hash ] The JSON data from the DB associated to the identifier # @return [ Hash ] The JSON data from the metadata associated to the identifier
def self.db_data(identifier) def self.metadata_at(identifier)
db[identifier] || {} metadata[identifier] || {}
end end
# @return [ JSON ] # @return [ JSON ]
def self.db def self.metadata
@db ||= read_json_file(db_file) @metadata ||= read_json_file(metadata_file)
end
# @return [ String ]
def self.metadata_file
@metadata_file ||= DB_DIR.join('metadata.json').to_s
end end
end end
end end

View File

@@ -6,17 +6,17 @@ module WPScan
class WpItems class WpItems
# @return [ Array<String> ] The slug of all items # @return [ Array<String> ] The slug of all items
def self.all_slugs def self.all_slugs
db.keys metadata.keys
end end
# @return [ Array<String> ] The slug of all popular items # @return [ Array<String> ] The slug of all popular items
def self.popular_slugs def self.popular_slugs
db.select { |_key, item| item['popular'] == true }.keys metadata.select { |_key, item| item['popular'] == true }.keys
end end
# @return [ Array<String> ] The slug of all vulnerable items # @return [ Array<String> ] The slug of all vulnerable items
def self.vulnerable_slugs def self.vulnerable_slugs
db.reject { |_key, item| item['vulnerabilities'].empty? }.keys metadata.select { |_key, item| item['vulnerabilities'] == true }.keys
end end
end end
end end

View File

@@ -4,9 +4,9 @@ module WPScan
module DB module DB
# WP Version # WP Version
class Version < WpItem class Version < WpItem
# @return [ String ] # @return [ Hash ]
def self.db_file def self.metadata
@db_file ||= DB_DIR.join('wordpresses.json').to_s @metadata ||= super['wordpress'] || {}
end end
end end
end end

View File

@@ -9,7 +9,9 @@ module WPScan
end end
end end
require_relative 'errors/enumeration'
require_relative 'errors/http' require_relative 'errors/http'
require_relative 'errors/update' require_relative 'errors/update'
require_relative 'errors/vuln_api'
require_relative 'errors/wordpress' require_relative 'errors/wordpress'
require_relative 'errors/xmlrpc' require_relative 'errors/xmlrpc'

View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true
module WPScan
module Error
class PluginsThresholdReached < Standard
def to_s
"The number of plugins detected reached the threshold of #{ParsedCli.plugins_threshold} " \
'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
'option to ignore the bad responses.'
end
end
class ThemesThresholdReached < Standard
def to_s
"The number of themes detected reached the threshold of #{ParsedCli.themes_threshold} " \
'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
'option to ignore the bad responses.'
end
end
end
end

View File

@@ -0,0 +1,20 @@
# frozen_string_literal: true
module WPScan
module Error
# Error raised when the token given via --api-token is invalid
class InvalidApiToken < Standard
def to_s
'The API token provided is invalid'
end
end
# Error raised when the number of API requests has been reached
# currently not implemented on the API side
class ApiLimitReached < Standard
def to_s
'Your API limit has been reached'
end
end
end
end

View File

@@ -5,7 +5,7 @@ module WPScan
# WordPress hosted (*.wordpress.com) # WordPress hosted (*.wordpress.com)
class WordPressHosted < Standard class WordPressHosted < Standard
def to_s def to_s
'Scanning *.wordpress.com hosted blogs is not supported.' 'The target appears to be hosted on WordPress.com. Scanning such site is not supported.'
end end
end end
@@ -25,7 +25,8 @@ module WPScan
class WpContentDirNotDetected < Standard class WpContentDirNotDetected < Standard
def to_s def to_s
'Unable to identify the wp-content dir, please supply it with --wp-content-dir' 'Unable to identify the wp-content dir, please supply it with --wp-content-dir,' \
' use the --scope option or make sure the --url value given is the correct one'
end end
end end
end end

View File

@@ -4,9 +4,9 @@ module WPScan
module Finders module Finders
module DynamicFinder module DynamicFinder
module Version module Version
# Version finder using Body Pattern method. Tipically used when the response is not # Version finder using Body Pattern method. Typically used when the response is not
# an HTML doc and Xpath can't be used # an HTML doc and Xpath can't be used
class BodyPattern < WPScan::Finders::DynamicFinder::Version::Finder class BodyPattern < Finders::DynamicFinder::Version::Finder
# @return [ Hash ] # @return [ Hash ]
def self.child_class_constants def self.child_class_constants
@child_class_constants ||= super().merge(PATTERN: nil, CONFIDENCE: 60) @child_class_constants ||= super().merge(PATTERN: nil, CONFIDENCE: 60)
@@ -16,7 +16,7 @@ module WPScan
# @param [ Hash ] opts # @param [ Hash ] opts
# @return [ Version ] # @return [ Version ]
def find(response, _opts = {}) def find(response, _opts = {})
return unless response.body =~ self.class::PATTERN return unless response.code != 404 && response.body =~ self.class::PATTERN
create_version( create_version(
Regexp.last_match[:v], Regexp.last_match[:v],

View File

@@ -6,7 +6,7 @@ module WPScan
module Version module Version
# Version finder in Comment, which is basically an Xpath one with a default # Version finder in Comment, which is basically an Xpath one with a default
# Xpath of //comment() # Xpath of //comment()
class Comment < WPScan::Finders::DynamicFinder::Version::Xpath class Comment < Finders::DynamicFinder::Version::Xpath
# @return [ Hash ] # @return [ Hash ]
def self.child_class_constants def self.child_class_constants
@child_class_constants ||= super().merge(PATTERN: nil, XPATH: '//comment()') @child_class_constants ||= super().merge(PATTERN: nil, XPATH: '//comment()')

View File

@@ -6,7 +6,7 @@ module WPScan
module Version module Version
# Version finder using by parsing config files, such as composer.json # Version finder using by parsing config files, such as composer.json
# and so on # and so on
class ConfigParser < WPScan::Finders::DynamicFinder::Version::Finder class ConfigParser < Finders::DynamicFinder::Version::Finder
ALLOWED_PARSERS = [JSON, YAML].freeze ALLOWED_PARSERS = [JSON, YAML].freeze
def self.child_class_constants def self.child_class_constants

View File

@@ -5,7 +5,7 @@ module WPScan
module DynamicFinder module DynamicFinder
module Version module Version
# Version finder using Header Pattern method # Version finder using Header Pattern method
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::Finder class HeaderPattern < Finders::DynamicFinder::Version::Finder
# @return [ Hash ] # @return [ Hash ]
def self.child_class_constants def self.child_class_constants
@child_class_constants ||= super().merge(HEADER: nil, PATTERN: nil, CONFIDENCE: 60) @child_class_constants ||= super().merge(HEADER: nil, PATTERN: nil, CONFIDENCE: 60)

View File

@@ -5,7 +5,7 @@ module WPScan
module DynamicFinder module DynamicFinder
module Version module Version
# Version finder using JavaScript Variable method # Version finder using JavaScript Variable method
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::Finder class JavascriptVar < Finders::DynamicFinder::Version::Finder
# @return [ Hash ] # @return [ Hash ]
def self.child_class_constants def self.child_class_constants
@child_class_constants ||= super().merge( @child_class_constants ||= super().merge(

View File

@@ -5,7 +5,7 @@ module WPScan
module DynamicFinder module DynamicFinder
module Version module Version
# Version finder using QueryParameter method # Version finder using QueryParameter method
class QueryParameter < WPScan::Finders::DynamicFinder::Version::Finder class QueryParameter < Finders::DynamicFinder::Version::Finder
# @return [ Hash ] # @return [ Hash ]
def self.child_class_constants def self.child_class_constants
@child_class_constants ||= super().merge( @child_class_constants ||= super().merge(
@@ -35,15 +35,13 @@ module WPScan
def scan_response(response) def scan_response(response)
found = {} found = {}
target.in_scope_urls(response, xpath) do |url, _tag| target.in_scope_uris(response, xpath) do |uri|
uri = Addressable::URI.parse(url)
next unless uri.path =~ path_pattern && uri.query&.match(self.class::PATTERN) next unless uri.path =~ path_pattern && uri.query&.match(self.class::PATTERN)
version = Regexp.last_match[:v].to_s version = Regexp.last_match[:v].to_s
found[version] ||= [] found[version] ||= []
found[version] << url found[version] << uri.to_s
end end
found found

View File

@@ -5,7 +5,7 @@ module WPScan
module DynamicFinder module DynamicFinder
module Version module Version
# Version finder using Xpath method # Version finder using Xpath method
class Xpath < WPScan::Finders::DynamicFinder::Version::Finder class Xpath < Finders::DynamicFinder::Version::Finder
# @return [ Hash ] # @return [ Hash ]
def self.child_class_constants def self.child_class_constants
@child_class_constants ||= super().merge( @child_class_constants ||= super().merge(

View File

@@ -4,22 +4,22 @@ module WPScan
module Finders module Finders
module DynamicFinder module DynamicFinder
module WpItemVersion module WpItemVersion
class BodyPattern < WPScan::Finders::DynamicFinder::Version::BodyPattern class BodyPattern < Finders::DynamicFinder::Version::BodyPattern
end end
class Comment < WPScan::Finders::DynamicFinder::Version::Comment class Comment < Finders::DynamicFinder::Version::Comment
end end
class ConfigParser < WPScan::Finders::DynamicFinder::Version::ConfigParser class ConfigParser < Finders::DynamicFinder::Version::ConfigParser
end end
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::HeaderPattern class HeaderPattern < Finders::DynamicFinder::Version::HeaderPattern
end end
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::JavascriptVar class JavascriptVar < Finders::DynamicFinder::Version::JavascriptVar
end end
class QueryParameter < WPScan::Finders::DynamicFinder::Version::QueryParameter class QueryParameter < Finders::DynamicFinder::Version::QueryParameter
# @return [ Regexp ] # @return [ Regexp ]
def path_pattern def path_pattern
# TODO: consider the target.blog.themes_dir if the target is a Theme (maybe implement a WpItem#item_dir ?) # TODO: consider the target.blog.themes_dir if the target is a Theme (maybe implement a WpItem#item_dir ?)
@@ -37,7 +37,7 @@ module WPScan
end end
end end
class Xpath < WPScan::Finders::DynamicFinder::Version::Xpath class Xpath < Finders::DynamicFinder::Version::Xpath
end end
end end
end end

View File

@@ -12,23 +12,23 @@ module WPScan
end end
end end
class BodyPattern < WPScan::Finders::DynamicFinder::Version::BodyPattern class BodyPattern < Finders::DynamicFinder::Version::BodyPattern
include Finder include Finder
end end
class Comment < WPScan::Finders::DynamicFinder::Version::Comment class Comment < Finders::DynamicFinder::Version::Comment
include Finder include Finder
end end
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::HeaderPattern class HeaderPattern < Finders::DynamicFinder::Version::HeaderPattern
include Finder include Finder
end end
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::JavascriptVar class JavascriptVar < Finders::DynamicFinder::Version::JavascriptVar
include Finder include Finder
end end
class QueryParameter < WPScan::Finders::DynamicFinder::Version::QueryParameter class QueryParameter < Finders::DynamicFinder::Version::QueryParameter
include Finder include Finder
# @return [ Hash ] # @return [ Hash ]

View File

@@ -6,13 +6,15 @@ rescue StandardError => e
raise "JSON parsing error in #{file} #{e}" raise "JSON parsing error in #{file} #{e}"
end end
# @return [ Symbol ] # Sanitize and classify a slug
# @note As a class can not start with a digit or underscore, a D_ is # @note As a class can not start with a digit or underscore, a D_ is
# put as a prefix in such case. Ugly but well :x # put as a prefix in such case. Ugly but well :x
# Not only used to classify slugs though, but Dynamic Finder names as well # Not only used to classify slugs though, but Dynamic Finder names as well
#
# @return [ Symbol ]
def classify_slug(slug) def classify_slug(slug)
classified = slug.to_s.tr('-', '_').camelize.to_s classified = slug.to_s.gsub(/[^a-z\d\-]/i, '-').gsub(/\-{1,}/, '_').camelize.to_s
classified = "D_#{classified}" if classified[0] =~ /\d/ classified = "D_#{classified}" if /\d/.match?(classified[0])
classified.to_sym classified.to_sym
end end

7
lib/wpscan/parsed_cli.rb Normal file
View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module WPScan
# To be able to use ParsedCli directly, rather than having to access it via WPscan::ParsedCli
class ParsedCli < CMSScanner::ParsedCli
end
end

View File

@@ -24,20 +24,20 @@ module WPScan
# #
# @return [ Boolean ] # @return [ Boolean ]
def wordpress?(detection_mode) def wordpress?(detection_mode)
in_scope_urls(homepage_res) do |url| in_scope_uris(homepage_res) do |uri|
return true if Addressable::URI.parse(url).path.match(WORDPRESS_PATTERN) return true if uri.path.match(WORDPRESS_PATTERN)
end end
homepage_res.html.css('meta[name="generator"]').each do |node| homepage_res.html.css('meta[name="generator"]').each do |node|
return true if node['content'] =~ /wordpress/i return true if /wordpress/i.match?(node['content'])
end end
return true unless comments_from_page(/wordpress/i, homepage_res).empty? return true unless comments_from_page(/wordpress/i, homepage_res).empty?
if %i[mixed aggressive].include?(detection_mode) if %i[mixed aggressive].include?(detection_mode)
%w[wp-admin/install.php wp-login.php].each do |path| %w[wp-admin/install.php wp-login.php].each do |path|
in_scope_urls(Browser.get_and_follow_location(url(path))).each do |url| in_scope_uris(Browser.get_and_follow_location(url(path))).each do |uri|
return true if Addressable::URI.parse(url).path.match(WORDPRESS_PATTERN) return true if uri.path.match(WORDPRESS_PATTERN)
end end
end end
end end
@@ -45,13 +45,52 @@ module WPScan
false false
end end
COOKIE_PATTERNS = {
'vjs' => /createCookie\('vjs','(?<c_value>\d+)',\d+\);/i
}.freeze
# Sometimes there is a mechanism in place on the blog, which requires a specific
# cookie and value to be added to requests. Lets try to detect and add them
def maybe_add_cookies
COOKIE_PATTERNS.each do |cookie_key, pattern|
next unless homepage_res.body =~ pattern
browser = Browser.instance
cookie_string = "#{cookie_key}=#{Regexp.last_match[:c_value]}"
cookie_string += "; #{browser.cookie_string}" if browser.cookie_string
browser.cookie_string = cookie_string
# Force recheck of the homepage when retying wordpress?
# No need to clear the cache, as the request (which will contain the cookies)
# will be different
@homepage_res = nil
@homepage_url = nil
break
end
end
# @return [ String ] # @return [ String ]
def registration_url def registration_url
multisite? ? url('wp-signup.php') : url('wp-login.php?action=register') multisite? ? url('wp-signup.php') : url('wp-login.php?action=register')
end end
# @return [ Boolean ] Whether or not the target is hosted on wordpress.com
def wordpress_hosted? def wordpress_hosted?
uri.host =~ /\.wordpress\.com$/i ? true : false return true if /\.wordpress\.com$/i.match?(uri.host)
unless content_dir(:passive)
pattern = %r{https?://s\d\.wp\.com#{WORDPRESS_PATTERN}}i.freeze
uris_from_page(homepage_res) do |uri|
return true if uri.to_s.match?(pattern)
end
end
false
end end
# @param [ String ] username # @param [ String ] username
@@ -70,6 +109,7 @@ module WPScan
Browser.instance.forge_request( Browser.instance.forge_request(
login_url, login_url,
method: :post, method: :post,
cache_ttl: 0,
body: { log: username, pwd: password } body: { log: username, pwd: password }
) )
end end

View File

@@ -13,24 +13,36 @@ module WPScan
@plugins_dir = dir.chomp('/') @plugins_dir = dir.chomp('/')
end end
# @param [ Symbol ] detection_mode
# @return [ String ] The wp-content directory # @return [ String ] The wp-content directory
def content_dir def content_dir(detection_mode = :mixed)
unless @content_dir unless @content_dir
escaped_url = Regexp.escape(url).gsub(/https?/i, 'https?') # scope_url_pattern is from CMSScanner::Target
pattern = %r{#{escaped_url}([\w\s\-\/]+)\/(?:themes|plugins|uploads|cache)\/}i pattern = %r{#{scope_url_pattern}([\w\s\-/]+)\\?/(?:themes|plugins|uploads|cache)\\?/}i
in_scope_urls(homepage_res) do |url| in_scope_uris(homepage_res) do |uri|
return @content_dir = Regexp.last_match[1] if url.match(pattern) return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
end end
xpath_pattern_from_page('//script[not(@src)]', pattern, homepage_res) do |match| # Checks for the pattern in raw JS code, as well as @content attributes of meta tags
xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, homepage_res) do |match|
return @content_dir = match[1] return @content_dir = match[1]
end end
unless detection_mode == :passive
return @content_dir = 'wp-content' if default_content_dir_exists?
end
end end
@content_dir @content_dir
end end
def default_content_dir_exists?
# url('wp-content') can't be used here as the folder has not yet been identified
# and the method would try to replace it by nil which would raise an error
[200, 401, 403].include?(Browser.forge_request(uri.join('wp-content/').to_s, head_or_get_params).run.code)
end
# @return [ Addressable::URI ] # @return [ Addressable::URI ]
def content_uri def content_uri
uri.join("#{content_dir}/") uri.join("#{content_dir}/")
@@ -85,23 +97,21 @@ module WPScan
themes_uri.join("#{URI.encode(slug)}/").to_s themes_uri.join("#{URI.encode(slug)}/").to_s
end end
# TODO: Factorise the code and the content_dir one ?
# @return [ String, False ] String of the sub_dir found, false otherwise # @return [ String, False ] String of the sub_dir found, false otherwise
# @note: nil can not be returned here, otherwise if there is no sub_dir # @note: nil can not be returned here, otherwise if there is no sub_dir
# the check would be done each time # the check would be done each time, which would make enumeration of
# long list of items very slow to generate
def sub_dir def sub_dir
unless @sub_dir return @sub_dir unless @sub_dir.nil?
escaped_url = Regexp.escape(url).gsub(/https?/i, 'https?')
pattern = %r{#{escaped_url}(.+?)\/(?:xmlrpc\.php|wp\-includes\/)}i
in_scope_urls(homepage_res) do |url| # url_pattern is from CMSScanner::Target
return @sub_dir = Regexp.last_match[1] if url.match(pattern) pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
end
@sub_dir = false in_scope_uris(homepage_res) do |uri|
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
end end
@sub_dir @sub_dir = false
end end
# Override of the WebSite#url to consider the custom WP directories # Override of the WebSite#url to consider the custom WP directories
@@ -112,9 +122,9 @@ module WPScan
def url(path = nil) def url(path = nil)
return @uri.to_s unless path return @uri.to_s unless path
if path =~ %r{wp\-content/plugins}i if %r{wp\-content/plugins}i.match?(path)
path = +path.gsub('wp-content/plugins', plugins_dir) path = +path.gsub('wp-content/plugins', plugins_dir)
elsif path =~ /wp\-content/i elsif /wp\-content/i.match?(path)
path = +path.gsub('wp-content', content_dir) path = +path.gsub('wp-content', content_dir)
elsif path[0] != '/' && sub_dir elsif path[0] != '/' && sub_dir
path = "#{sub_dir}/#{path}" path = "#{sub_dir}/#{path}"

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
module Typhoeus
# Custom Response class
class Response
# @note: Ignores requests done to the /status endpoint of the API
#
# @return [ Boolean ]
def from_vuln_api?
effective_url.start_with?(WPScan::DB::VulnApi.uri.to_s) && !effective_url.include?('/status')
end
end
end

View File

@@ -2,5 +2,5 @@
# Version # Version
module WPScan module WPScan
VERSION = '3.5.0' VERSION = '3.7.1'
end end

View File

@@ -3,12 +3,10 @@
describe WPScan::Controller::Aliases do describe WPScan::Controller::Aliases do
subject(:controller) { described_class.new } subject(:controller) { described_class.new }
let(:target_url) { 'http://ex.lo/' } let(:target_url) { 'http://ex.lo/' }
let(:parsed_options) { rspec_parsed_options(cli_args) }
let(:cli_args) { "--url #{target_url}" } let(:cli_args) { "--url #{target_url}" }
before do before do
WPScan::Browser.reset WPScan::ParsedCli.options = rspec_parsed_options(cli_args)
described_class.parsed_options = parsed_options
end end
describe '#cli_options' do describe '#cli_options' do
@@ -22,14 +20,18 @@ describe WPScan::Controller::Aliases do
describe 'parsed_options' do describe 'parsed_options' do
context 'when no --stealthy supplied' do context 'when no --stealthy supplied' do
its(:parsed_options) { should eql parsed_options } it 'contains the correct options' do
expect(WPScan::ParsedCli.options).to include(
detection_mode: :mixed, plugins_version_detection: :mixed
)
end
end end
context 'when --stealthy supplied' do context 'when --stealthy supplied' do
let(:cli_args) { "#{super()} --stealthy" } let(:cli_args) { "#{super()} --stealthy" }
it 'contains the correct options' do it 'contains the correct options' do
expect(controller.parsed_options).to include( expect(WPScan::ParsedCli.options).to include(
random_user_agent: true, detection_mode: :passive, plugins_version_detection: :passive random_user_agent: true, detection_mode: :passive, plugins_version_detection: :passive
) )
end end

View File

@@ -3,13 +3,11 @@
describe WPScan::Controller::Core do describe WPScan::Controller::Core do
subject(:core) { described_class.new } subject(:core) { described_class.new }
let(:target_url) { 'http://ex.lo/' } let(:target_url) { 'http://ex.lo/' }
let(:parsed_options) { rspec_parsed_options(cli_args) }
let(:cli_args) { "--url #{target_url}" } let(:cli_args) { "--url #{target_url}" }
before do before do
WPScan::Browser.reset
described_class.reset described_class.reset
described_class.parsed_options = parsed_options WPScan::ParsedCli.options = rspec_parsed_options(cli_args)
end end
describe '#cli_options' do describe '#cli_options' do
@@ -140,7 +138,7 @@ describe WPScan::Controller::Core do
expect(core.formatter).to receive(:output).with('banner', hash_including(verbose: nil), 'core') expect(core.formatter).to receive(:output).with('banner', hash_including(verbose: nil), 'core')
expect(core).to receive(:update_db_required?).and_return(false) unless parsed_options[:update] expect(core).to receive(:update_db_required?).and_return(false) unless WPScan::ParsedCli.update
end end
context 'when --update' do context 'when --update' do
@@ -218,7 +216,7 @@ describe WPScan::Controller::Core do
context 'when not wordpress' do context 'when not wordpress' do
it 'raises an error' do it 'raises an error' do
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false) expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress) expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
end end
@@ -250,12 +248,26 @@ describe WPScan::Controller::Core do
context 'when not wordpress' do context 'when not wordpress' do
before do before do
expect(core).to receive(:load_server_module) expect(core).to receive(:load_server_module)
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false)
end end
context 'when no --force' do context 'when no --force' do
it 'raises an error' do before { expect(core.target).to receive(:maybe_add_cookies) }
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
context 'when no cookies added or still not wordpress after being added' do
it 'raises an error' do
expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
end
end
context 'when the added cookies solved it' do
it 'does not raise an error' do
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false).ordered
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true).ordered
expect { core.before_scan }.to_not raise_error
end
end end
end end
@@ -263,6 +275,8 @@ describe WPScan::Controller::Core do
let(:cli_args) { "#{super()} --force" } let(:cli_args) { "#{super()} --force" }
it 'does not raise any error' do it 'does not raise any error' do
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false)
expect { core.before_scan }.to_not raise_error expect { core.before_scan }.to_not raise_error
end end
end end

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