Compare commits

...

187 Commits

Author SHA1 Message Date
erwanlr
4340d27258 Bumps version 2020-01-21 16:14:26 +00:00
erwanlr
e911be8f14 Removes ruby 2.7.0 from failure matrix 2020-01-21 16:12:38 +00:00
erwanlr
a4c650cdff Fixes incorrect RDF URLs detection 2020-01-21 15:06:29 +00:00
erwanlr
31a58f8a8f Fixes rare crash 2020-01-21 14:47:17 +00:00
erwanlr
ba4f15f111 Adds DFs 2020-01-18 11:20:43 +00:00
Erwan
206a913eb9 Merge pull request #1447 from wpscanteam/dependabot/bundler/webmock-tw-3.8.0
Update webmock requirement from ~> 3.7.0 to ~> 3.8.0
2020-01-16 10:17:09 +01:00
dependabot-preview[bot]
21ba490073 Update webmock requirement from ~> 3.7.0 to ~> 3.8.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.7.0...v3.8.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-16 05:23:09 +00:00
erwanlr
2a29e2ed95 Adds DFs 2020-01-11 19:54:12 +00:00
Erwan
9517d14fd3 Fixes rubocop offence 2020-01-10 20:06:33 +01:00
Erwan
3deaa896df Fix rubocop typo 2020-01-10 19:28:01 +01:00
Erwan
c117007dc0 Merge pull request #1445 from wpscanteam/dependabot/bundler/rubocop-tw-0.79.0
Update rubocop requirement from ~> 0.78.0 to ~> 0.79.0
2020-01-07 08:53:56 +01:00
dependabot-preview[bot]
50baa238b9 Update rubocop requirement from ~> 0.78.0 to ~> 0.79.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.78.0...v0.79.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 05:21:29 +00:00
erwanlr
0e2d771660 Adds DFs 2020-01-04 12:17:51 +00:00
erwanlr
32b4670755 Bumps version 2020-01-02 15:28:33 +00:00
erwanlr
4a032d5e12 Merge branch 'ruby-2.7' 2020-01-02 14:53:46 +00:00
erwanlr
5887fede15 Updates deps 2020-01-02 13:53:28 +00:00
erwanlr
ad4eeb9f81 Fixes specs 2020-01-02 13:29:30 +00:00
erwanlr
a62c16d7cc Fixes Rubocop warning 2020-01-02 13:04:01 +00:00
erwanlr
e766e7392a Updates CMScanner dep 2020-01-02 12:52:03 +00:00
erwanlr
025c9c24ca Fixes GH Action 2020-01-02 12:51:23 +00:00
erwanlr
ab052add27 Allows Travis to fail on ruby-2.7 2019-12-28 13:14:19 +00:00
erwanlr
15cb99977b Fixes #1444 2019-12-28 13:10:57 +00:00
erwanlr
82d5af926f Adds Ruby 2.7.0 to Travis 2019-12-28 12:27:20 +00:00
erwanlr
76f73f3dc8 Adds DFs 2019-12-28 11:38:10 +00:00
erwanlr
575b22320e Merge branch 'df' 2019-12-21 10:49:32 +00:00
erwanlr
d20c07dc85 Adds DFs 2019-12-21 10:49:11 +00:00
erwanlr
f89071b87a Adds DFs, Updates rspec behaviour regarding --fail-fast 2019-12-20 15:47:56 +00:00
erwanlr
8b4e90f285 Updates deps + fixes travis 2019-12-19 13:56:06 +00:00
erwanlr
9c4f57c786 Adds DFs 2019-12-19 13:55:21 +00:00
erwanlr
902ec24b77 Adds DFs 2019-12-19 07:55:39 +00:00
erwanlr
7eba77fa63 Displays status code for KnownLocation finders 2019-12-15 09:00:30 +00:00
erwanlr
0753bbf7b3 Adds DF 2019-12-15 08:47:47 +00:00
erwanlr
6b2333614a Adds DFs 2019-12-14 12:14:37 +00:00
erwanlr
80b7f458f5 Fixes #1435 2019-12-08 10:34:18 +00:00
erwanlr
dbd8e59cf4 Typo 2019-12-07 15:46:31 +00:00
erwanlr
9948230ea0 Adds Dfs 2019-12-07 15:40:40 +00:00
Erwan
e2c858ac69 Merge pull request #1434 from crweiner/api-link
Fix links to API signup page
2019-12-03 13:37:55 +01:00
chandler
bac8b613e6 Fix links to API signup page 2019-12-02 17:03:36 -05:00
erwanlr
abbae15c6f Adds DFs 2019-11-30 12:47:32 +00:00
erwanlr
1548e8bfc1 Updates DFs 2019-11-28 20:19:25 +00:00
erwanlr
dc8cf3fc34 Disables a DF causing FP when checking the WP version 2019-11-28 17:15:05 +00:00
Erwan
c3cd815567 Merge pull request #1433 from wpscanteam/dependabot/bundler/rubocop-tw-0.77.0
Update rubocop requirement from ~> 0.76.0 to ~> 0.77.0
2019-11-28 08:50:50 +01:00
dependabot-preview[bot]
ce543b9384 Update rubocop requirement from ~> 0.76.0 to ~> 0.77.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.76.0...v0.77.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-28 05:21:09 +00:00
Christian Mehlmauer
9755c8cf42 Delete rubocop.yml 2019-11-26 23:33:52 +01:00
Christian Mehlmauer
434a210fb5 Update and rename rspec.yml to ruby.yml 2019-11-26 23:33:37 +01:00
Christian Mehlmauer
587602665a Update rubocop.yml 2019-11-26 23:29:30 +01:00
Christian Mehlmauer
bfec63df41 Update rspec.yml 2019-11-26 23:26:35 +01:00
Christian Mehlmauer
3b150df1af Update rspec.yml 2019-11-26 23:25:36 +01:00
Christian Mehlmauer
f24ecf0537 Update rubocop.yml 2019-11-26 23:23:13 +01:00
Christian Mehlmauer
9ddecbcc0a Update and rename ruby.yml to rspec.yml 2019-11-26 23:22:46 +01:00
Christian Mehlmauer
947bb8d3d5 Update ruby.yml 2019-11-26 23:18:33 +01:00
Christian Mehlmauer
30cbf87b35 Update gempush.yml 2019-11-26 23:14:53 +01:00
Christian Mehlmauer
69c3aab35a Update .dockerignore 2019-11-26 23:06:05 +01:00
Christian Mehlmauer
bdeb3547f1 Create rubocop.yml 2019-11-26 23:05:45 +01:00
Christian Mehlmauer
99e04b9669 Create ruby.yml 2019-11-26 23:04:03 +01:00
Christian Mehlmauer
680d2fb7eb add gem push 2019-11-26 23:03:23 +01:00
erwanlr
8814eda018 Adds DFs 2019-11-23 11:45:15 +00:00
erwanlr
7e72ba2885 Adds DFs 2019-11-16 11:18:23 +00:00
Christian Mehlmauer
b4d7a8490b change twitter handle 2019-11-13 08:23:18 +01:00
erwanlr
e9a5bc66df Bumps version 2019-11-11 12:41:29 +00:00
erwanlr
edebc77726 Fixes 1426 2019-11-09 17:55:31 +00:00
erwanlr
271dee824d Adds DFs 2019-11-09 10:59:46 +00:00
erwanlr
1e868d10ca Ignores slow specs when executing rake tasks 2019-11-05 20:11:03 +00:00
erwanlr
4be3f17ae4 Bumps version 2019-11-05 19:19:18 +00:00
erwanlr
f24e7be264 Updates dep 2019-11-05 18:47:41 +00:00
erwanlr
9adc26445d Adds DFs 2019-11-02 11:47:11 +00:00
erwanlr
353e7dcbb9 Removes ruby-head from Travis 2019-11-01 17:47:05 +00:00
erwanlr
430e65c12e Updates cms_scanner dep 2019-11-01 16:43:13 +00:00
erwanlr
1aa242a9d8 Avoids duplicates in finding the same thing diffrent pages 2019-11-01 15:20:46 +00:00
erwanlr
7173cd85fe Changes "Detected By" to "Found By" in cli output for consistency with JSON output 2019-11-01 10:52:31 +00:00
erwanlr
b95a4f55e3 Adds DFs 2019-11-01 10:32:02 +00:00
erwanlr
6b5e016770 Improves detection of WP Version, Plugins etc by checking 404 2019-11-01 09:47:44 +00:00
erwanlr
85aa9f61cd Center the banner text 2019-10-31 16:42:40 +00:00
erwanlr
5c187002d6 Updates cms_scanner dep 2019-10-31 16:27:41 +00:00
Erwan
9bc373308b Merge pull request #1420 from wpscanteam/dependabot/bundler/rubocop-tw-0.76.0
Update rubocop requirement from ~> 0.75.0 to ~> 0.76.0
2019-10-29 07:52:15 +01:00
dependabot-preview[bot]
cdeb0fc144 Update rubocop requirement from ~> 0.75.0 to ~> 0.76.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.75.0...v0.76.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-29 05:26:34 +00:00
ethicalhack3r
f1acdd9389 Remove 'no monetary cost' from LICENSE file 2019-10-28 16:42:15 +01:00
erwanlr
d6fac6a210 Adds DFs 2019-10-26 12:29:41 +01:00
Erwan
007cfb0801 Updates Ruby versions for Travis (now only latest in stable) 2019-10-26 10:58:56 +02:00
erwanlr
1f9829b7c0 Adds DFs 2019-10-19 22:56:38 +01:00
Erwan
e039d22565 Fix #1413 2019-10-17 20:31:28 +02:00
erwanlr
b0775b1610 Adds DFs 2019-10-15 17:18:40 +01:00
erwanlr
0e429700c6 Fixes #1412 2019-10-12 11:48:14 +01:00
erwanlr
af7804ca23 Adds DF 2019-10-12 10:54:21 +01:00
erwanlr
9da326967b Bumps version 2019-10-11 15:07:50 +01:00
erwanlr
62600b3a66 Fixes #1411 2019-10-11 14:32:00 +01:00
erwanlr
b236138fb5 Adds additional specs related to previous commit 2019-10-10 22:57:26 +01:00
erwanlr
40c2e9a54b Fixes non detection of plugins/themes from homepage in some cases 2019-10-10 22:33:19 +01:00
erwanlr
a9062db57f Improves detection speed of plugins/themes from homepage 2019-10-10 21:15:59 +01:00
erwanlr
2621404c5f Removes useless spec comment 2019-10-10 21:03:54 +01:00
erwanlr
c47211ca79 Fixes empty username returned in some cases 2019-10-10 20:34:39 +01:00
erwanlr
e39a192e8d Checks default wp-content dir regardless of detection mode if not found passively 2019-10-10 19:59:09 +01:00
Erwan
d85035d5ef Merge pull request #1407 from wpscanteam/dependabot/bundler/rspec-tw-3.9.0
Update rspec requirement from ~> 3.8.0 to ~> 3.9.0
2019-10-09 12:50:59 +02:00
dependabot-preview[bot]
de09a97343 Update rspec requirement from ~> 3.8.0 to ~> 3.9.0
Updates the requirements on [rspec](https://github.com/rspec/rspec) to permit the latest version.
- [Release notes](https://github.com/rspec/rspec/releases)
- [Commits](https://github.com/rspec/rspec/compare/v3.8.0...v3.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-09 05:48:37 +00:00
erwanlr
a6855345d7 Fixes #1406 2019-10-07 07:03:06 +01:00
erwanlr
a53f88b626 Improves WP detection 2019-10-06 16:51:35 +01:00
erwanlr
7048c82124 Adds DFs 2019-10-06 10:46:12 +01:00
erwanlr
6aa7cda478 Fixes #1404 2019-10-05 20:25:35 +01:00
erwanlr
ff339b9a8c Updates rubocop config 2019-10-03 13:09:14 +01:00
Erwan
8898cc20fe Merge pull request #1403 from wpscanteam/dependabot/bundler/rubocop-performance-tw-1.5.0
Update rubocop-performance requirement from ~> 1.4.0 to ~> 1.5.0
2019-10-02 18:35:11 +02:00
dependabot-preview[bot]
770d1da280 Update rubocop-performance requirement from ~> 1.4.0 to ~> 1.5.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.4.0...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-02 06:46:49 +00:00
Erwan
6ba4e8a29b Merge pull request #1402 from wpscanteam/dependabot/bundler/rubocop-tw-0.75.0
Update rubocop requirement from ~> 0.74.0 to ~> 0.75.0
2019-10-02 08:45:36 +02:00
dependabot-preview[bot]
953ca68495 Update rubocop requirement from ~> 0.74.0 to ~> 0.75.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.74.0...v0.75.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-01 06:21:21 +00:00
Erwan
4289dfb37d Merge pull request #1401 from wpscanteam/dependabot/bundler/rake-tw-13.0
Update rake requirement from ~> 12.3 to ~> 13.0
2019-09-30 12:37:15 +02:00
dependabot-preview[bot]
4f6f2f436a Update rake requirement from ~> 12.3 to ~> 13.0
Updates the requirements on [rake](https://github.com/ruby/rake) to permit the latest version.
- [Release notes](https://github.com/ruby/rake/releases)
- [Changelog](https://github.com/ruby/rake/blob/master/History.rdoc)
- [Commits](https://github.com/ruby/rake/compare/v12.3.0...v13.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-30 05:44:30 +00:00
erwanlr
237979a479 Adds DFs 2019-09-28 17:29:35 +01:00
erwanlr
2e48968fd3 Bumps version 2019-09-25 16:00:24 +01:00
erwanlr
9a0c4a5c8f Fixes #1399 2019-09-25 10:02:15 +01:00
Erwan
9a011f0007 Merge pull request #1397 from noplanman/fix_registration_link
Fix broken registration / signup link.
2019-09-25 10:52:06 +02:00
Armando Lüscher
3f907a706f Fix broken registration / signup link. 2019-09-24 23:19:47 +02:00
erwanlr
9446141716 Adds DFs 2019-09-21 10:20:59 +01:00
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
543 changed files with 182727 additions and 821 deletions

View File

@@ -14,3 +14,4 @@ Dockerfile
*.orig *.orig
bin/wpscan-* bin/wpscan-*
.wpscan/ .wpscan/
.github/

40
.github/workflows/gempush.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Ruby Gem
on:
release:
types: [published]
jobs:
build:
name: Build + Publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Set up Ruby 2.6
uses: actions/setup-ruby@v1
with:
ruby-version: 2.6.x
#- name: Publish to GPR
# run: |
# mkdir -p $HOME/.gem
# touch $HOME/.gem/credentials
# chmod 0600 $HOME/.gem/credentials
# printf -- "---\n:github: Bearer ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
# gem build *.gemspec
# gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
# env:
# GEM_HOST_API_KEY: ${{secrets.GITHUB_TOKEN}}
# OWNER: wpscanteam
- name: Publish to RubyGems
run: |
mkdir -p $HOME/.gem
touch $HOME/.gem/credentials
chmod 0600 $HOME/.gem/credentials
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
gem build *.gemspec
gem push *.gem
env:
GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}

34
.github/workflows/ruby.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Ruby
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Ruby 2.6
uses: actions/setup-ruby@v1
with:
ruby-version: 2.6.x
- name: Cache gems
uses: actions/cache@v1
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/wpscan.gemspec') }}
restore-keys: |
${{ runner.os }}-gem-
- name: Build and test
run: |
gem install bundler
bundle config force_ruby_platform true
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: test
run: |
bundle exec rspec
- name: rubocop
run: |
bundle exec rubocop

5
.rspec
View File

@@ -1,3 +1,2 @@
--color --require spec_helper
--fail-fast --color
--require spec_helper

View File

@@ -4,12 +4,8 @@ AllCops:
Exclude: Exclude:
- '*.gemspec' - '*.gemspec'
- 'vendor/**/*' - 'vendor/**/*'
ClassVars: Layout/LineLength:
Enabled: false
LineLength:
Max: 120 Max: 120
MethodLength:
Max: 20
Lint/UriEscapeUnescape: Lint/UriEscapeUnescape:
Enabled: false Enabled: false
Metrics/AbcSize: Metrics/AbcSize:
@@ -19,9 +15,20 @@ 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
Metrics/MethodLength:
Max: 20
Exclude:
- 'app/controllers/enumeration/cli_options.rb'
Style/ClassVars:
Enabled: false
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,29 +2,10 @@ language: ruby
sudo: false sudo: false
cache: bundler cache: bundler
rvm: rvm:
- 2.4.1 - 2.4.9
- 2.4.2 - 2.5.7
- 2.4.3 - 2.6.5
- 2.4.4 - 2.7.0
- 2.4.5
- 2.4.6
- 2.5.0
- 2.5.1
- 2.5.2
- 2.5.3
- 2.5.4
- 2.5.5
- 2.6.0
- 2.6.1
- 2.6.2
- 2.6.3
- ruby-head
before_install:
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
- gem update --system
matrix:
allow_failures:
- rvm: ruby-head
script: script:
- bundle exec rubocop - bundle exec rubocop
- bundle exec rspec - bundle exec rspec

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,7 +19,7 @@ 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
@@ -38,4 +38,3 @@ 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"]
CMD ["--help"]

View File

@@ -29,8 +29,6 @@ Example cases which do not require a commercial license, and thus fall under the
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org. If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
Free-use Terms and Conditions; Free-use Terms and Conditions;
3. Redistribution 3. Redistribution

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
@@ -78,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

@@ -6,14 +6,18 @@ exec = []
begin begin
require 'rubocop/rake_task' require 'rubocop/rake_task'
RuboCop::RakeTask.new RuboCop::RakeTask.new
exec << :rubocop exec << :rubocop
rescue LoadError rescue LoadError
end end
begin begin
require 'rspec/core/rake_task' require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
RSpec::Core::RakeTask.new(:spec) { |t| t.rspec_opts = %w{--tag ~slow} }
exec << :spec exec << :spec
rescue LoadError rescue LoadError
end end

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

@@ -18,7 +18,7 @@ module WPScan
target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir
target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir
return if target.content_dir(ParsedCli.detection_mode) return if target.content_dir
raise Error::WpContentDirNotDetected raise Error::WpContentDirNotDetected
end end

View File

@@ -7,15 +7,6 @@ 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 = ParsedCli.enumerate || {} enum = ParsedCli.enumerate || {}

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, advanced: true
) )
] ]
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, advanced: true
) )
] ]
end end

View File

@@ -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(ParsedCli.options), list: plugins_list_from_opts(ParsedCli.options),
threshold: ParsedCli.plugins_threshold,
sort: true sort: true
) )
@@ -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(ParsedCli.options), list: themes_list_from_opts(ParsedCli.options),
threshold: ParsedCli.themes_threshold,
sort: true sort: true
) )
@@ -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

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

@@ -4,7 +4,6 @@ module WPScan
module Finders module Finders
module DbExports module DbExports
# DB Exports finder # DB Exports finder
# See https://github.com/wpscanteam/wpscan-v3/issues/62
class KnownLocations < CMSScanner::Finders::Finder class KnownLocations < CMSScanner::Finders::Finder
include CMSScanner::Finders::Finder::Enumerator include CMSScanner::Finders::Finder::Enumerator
@@ -20,9 +19,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)
@@ -41,7 +40,7 @@ module WPScan
# @return [ Hash ] # @return [ Hash ]
def potential_urls(opts = {}) def potential_urls(opts = {})
urls = {} urls = {}
domain_name = target.uri.host[/(^[\w|-]+)/, 1] domain_name = PublicSuffix.domain(target.uri.host)[/(^[\w|-]+)/, 1]
File.open(opts[:list]).each_with_index do |path, index| File.open(opts[:list]).each_with_index do |path, index|
path.gsub!('{domain_name}', domain_name) path.gsub!('{domain_name}', domain_name)

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

@@ -10,7 +10,7 @@ module WPScan
pattern = %r{#{target.content_dir}/mu\-plugins/}i pattern = %r{#{target.content_dir}/mu\-plugins/}i
target.in_scope_uris(target.homepage_res) do |uri| target.in_scope_uris(target.homepage_res) do |uri|
next unless uri.path =~ pattern next unless uri.path&.match?(pattern)
url = target.url('wp-content/mu-plugins/') url = target.url('wp-content/mu-plugins/')

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

@@ -1,8 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'main_theme/css_style' require_relative 'main_theme/css_style_in_homepage'
require_relative 'main_theme/css_style_in_404_page'
require_relative 'main_theme/woo_framework_meta_generator' require_relative 'main_theme/woo_framework_meta_generator'
require_relative 'main_theme/urls_in_homepage' require_relative 'main_theme/urls_in_homepage'
require_relative 'main_theme/urls_in_404_page'
module WPScan module WPScan
module Finders module Finders
@@ -14,9 +16,11 @@ module WPScan
# @param [ WPScan::Target ] target # @param [ WPScan::Target ] target
def initialize(target) def initialize(target)
finders << finders <<
MainTheme::CssStyle.new(target) << MainTheme::CssStyleInHomepage.new(target) <<
MainTheme::CssStyleIn404Page.new(target) <<
MainTheme::WooFrameworkMetaGenerator.new(target) << MainTheme::WooFrameworkMetaGenerator.new(target) <<
MainTheme::UrlsInHomepage.new(target) MainTheme::UrlsInHomepage.new(target) <<
MainTheme::UrlsIn404Page.new(target)
end end
end end
end end

View File

@@ -0,0 +1,14 @@
# frozen_string_literal: true
module WPScan
module Finders
module MainTheme
# From the CSS style in the 404 page
class CssStyleIn404Page < CssStyleInHomepage
def passive(opts = {})
passive_from_css_href(target.error_404_res, opts) || passive_from_style_code(target.error_404_res, opts)
end
end
end
end
end

View File

@@ -3,9 +3,9 @@
module WPScan module WPScan
module Finders module Finders
module MainTheme module MainTheme
# From the css style # From the CSS style in the homepage
class CssStyle < CMSScanner::Finders::Finder class CssStyleInHomepage < CMSScanner::Finders::Finder
include Finders::WpItems::URLsInHomepage include Finders::WpItems::UrlsInPage # To have the item_code_pattern method available here
def create_theme(slug, style_url, opts) def create_theme(slug, style_url, opts)
Model::Theme.new( Model::Theme.new(

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
module WPScan
module Finders
module MainTheme
# URLs In 404 Page Finder
class UrlsIn404Page < UrlsInHomepage
# @return [ Typhoeus::Response ]
def page_res
@page_res ||= target.error_404_res
end
end
end
end
end

View File

@@ -5,7 +5,7 @@ module WPScan
module MainTheme module MainTheme
# URLs In Homepage Finder # URLs In Homepage Finder
class UrlsInHomepage < CMSScanner::Finders::Finder class UrlsInHomepage < CMSScanner::Finders::Finder
include WpItems::URLsInHomepage include WpItems::UrlsInPage
# @param [ Hash ] opts # @param [ Hash ] opts
# #
@@ -21,6 +21,11 @@ module WPScan
found found
end end
# @return [ Typhoeus::Response ]
def page_res
@page_res ||= target.homepage_res
end
end end
end end
end end

View File

@@ -10,7 +10,7 @@ module WPScan
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze
def passive(opts = {}) def passive(opts = {})
return unless target.homepage_res.body =~ PATTERN return unless target.homepage_res.body =~ PATTERN || target.error_404_res.body =~ PATTERN
Model::Theme.new( Model::Theme.new(
Regexp.last_match[1], Regexp.last_match[1],

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

@@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'plugins/urls_in_homepage' require_relative 'plugins/urls_in_homepage'
require_relative 'plugins/urls_in_404_page'
require_relative 'plugins/known_locations' require_relative 'plugins/known_locations'
# From the DynamicFinders # From the DynamicFinders
require_relative 'plugins/comment' require_relative 'plugins/comment'
@@ -22,6 +23,7 @@ module WPScan
def initialize(target) def initialize(target)
finders << finders <<
Plugins::UrlsInHomepage.new(target) << Plugins::UrlsInHomepage.new(target) <<
Plugins::UrlsIn404Page.new(target) <<
Plugins::HeaderPattern.new(target) << Plugins::HeaderPattern.new(target) <<
Plugins::Comment.new(target) << Plugins::Comment.new(target) <<
Plugins::Xpath.new(target) << Plugins::Xpath.new(target) <<

View File

@@ -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

@@ -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

@@ -19,8 +19,14 @@ module WPScan
def aggressive(opts = {}) def aggressive(opts = {})
found = [] found = []
enumerate(target_urls(opts), opts.merge(check_full_response: true)) 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)) finding_opts = opts.merge(found_by: found_by,
confidence: 80,
interesting_entries: ["#{res.effective_url}, status: #{res.code}"])
found << Model::Plugin.new(slug, target, finding_opts)
raise Error::PluginsThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
end end
found found

View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
module WPScan
module Finders
module Plugins
# URLs In 404 Page Finder
# Typically, the items detected from URLs like /wp-content/plugins/<slug>/
class UrlsIn404Page < UrlsInHomepage
# @return [ Typhoeus::Response ]
def page_res
@page_res ||= target.error_404_res
end
end
end
end
end

View File

@@ -4,10 +4,9 @@ module WPScan
module Finders module Finders
module Plugins module Plugins
# URLs In Homepage Finder # URLs In Homepage Finder
# Typically, the items detected from URLs like # Typically, the items detected from URLs like /wp-content/plugins/<slug>/
# /wp-content/plugins/<slug>/
class UrlsInHomepage < CMSScanner::Finders::Finder class UrlsInHomepage < CMSScanner::Finders::Finder
include WpItems::URLsInHomepage include WpItems::UrlsInPage
# @param [ Hash ] opts # @param [ Hash ] opts
# #
@@ -21,6 +20,11 @@ module WPScan
found found
end end
# @return [ Typhoeus::Response ]
def page_res
@page_res ||= target.homepage_res
end
end end
end end
end end

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

@@ -1,12 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'themes/urls_in_homepage' require_relative 'themes/urls_in_homepage'
require_relative 'themes/urls_in_404_page'
require_relative 'themes/known_locations' require_relative 'themes/known_locations'
module WPScan module WPScan
module Finders module Finders
module Themes module Themes
# themes Finder # Themes Finder
class Base class Base
include CMSScanner::Finders::SameTypeFinder include CMSScanner::Finders::SameTypeFinder
@@ -14,6 +15,7 @@ module WPScan
def initialize(target) def initialize(target)
finders << finders <<
Themes::UrlsInHomepage.new(target) << Themes::UrlsInHomepage.new(target) <<
Themes::UrlsIn404Page.new(target) <<
Themes::KnownLocations.new(target) Themes::KnownLocations.new(target)
end end
end end

View File

@@ -19,8 +19,14 @@ module WPScan
def aggressive(opts = {}) def aggressive(opts = {})
found = [] found = []
enumerate(target_urls(opts), opts.merge(check_full_response: true)) 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)) finding_opts = opts.merge(found_by: found_by,
confidence: 80,
interesting_entries: ["#{res.effective_url}, status: #{res.code}"])
found << Model::Theme.new(slug, target, finding_opts)
raise Error::ThemesThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
end end
found found

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
module WPScan
module Finders
module Themes
# URLs In 04 Page Finder
class UrlsIn404Page < UrlsInHomepage
# @return [ Typhoeus::Response ]
def page_res
@page_res ||= target.error_404_res
end
end
end
end
end

View File

@@ -5,7 +5,7 @@ module WPScan
module Themes module Themes
# URLs In Homepage Finder # URLs In Homepage Finder
class UrlsInHomepage < CMSScanner::Finders::Finder class UrlsInHomepage < CMSScanner::Finders::Finder
include WpItems::URLsInHomepage include WpItems::UrlsInPage
# @param [ Hash ] opts # @param [ Hash ] opts
# #
@@ -19,6 +19,11 @@ module WPScan
found found
end end
# @return [ Typhoeus::Response ]
def page_res
@page_res ||= target.homepage_res
end
end end
end end
end end

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

@@ -97,9 +97,12 @@ module WPScan
# @return [ String, nil ] # @return [ String, nil ]
def display_name_from_body(body) def display_name_from_body(body)
page = Nokogiri::HTML.parse(body) page = Nokogiri::HTML.parse(body)
# WP >= 3.0 # WP >= 3.0
page.css('h1.page-title span').each do |node| page.css('h1.page-title span').each do |node|
return node.text.to_s text = node.text.to_s.strip
return text unless text.empty?
end end
# WP < 3.0 # WP < 3.0

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

@@ -34,6 +34,8 @@ module WPScan
def user_details_from_oembed_data(oembed_data) def user_details_from_oembed_data(oembed_data)
return unless oembed_data return unless oembed_data
oembed_data = oembed_data.first if oembed_data.is_a?(Array)
if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z} if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z}
details = [Regexp.last_match[1], 'Author URL', 90] details = [Regexp.last_match[1], 'Author URL', 90]
elsif oembed_data['author_name'] && !oembed_data['author_name'].empty? elsif oembed_data['author_name'] && !oembed_data['author_name'].empty?

View File

@@ -1,3 +1,3 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'wp_items/urls_in_homepage' require_relative 'wp_items/urls_in_page'

View File

@@ -4,7 +4,7 @@ module WPScan
module Finders module Finders
module WpItems module WpItems
# URLs In Homepage Module to use in plugins & themes finders # URLs In Homepage Module to use in plugins & themes finders
module URLsInHomepage module UrlsInPage
# @param [ String ] type plugins / themes # @param [ String ] type plugins / themes
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results # @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
# #
@@ -12,10 +12,12 @@ module WPScan
def items_from_links(type, uniq = true) def items_from_links(type, uniq = true)
found = [] found = []
target.in_scope_uris(target.homepage_res) do |uri| target.in_scope_uris(page_res) do |uri|
next unless uri.to_s =~ item_attribute_pattern(type) next unless uri.to_s =~ item_attribute_pattern(type)
found << Regexp.last_match[1] slug = Regexp.last_match[1]&.strip
found << slug unless slug&.empty?
end end
uniq ? found.uniq.sort : found.sort uniq ? found.uniq.sort : found.sort
@@ -28,7 +30,7 @@ module WPScan
def items_from_codes(type, uniq = true) def items_from_codes(type, uniq = true)
found = [] found = []
target.homepage_res.html.css('script,style').each do |tag| page_res.html.xpath('//script[not(@src)]|//style[not(@src)]').each do |tag|
code = tag.text.to_s code = tag.text.to_s
next if code.empty? next if code.empty?
@@ -42,7 +44,7 @@ module WPScan
# #
# @return [ Regexp ] # @return [ Regexp ]
def item_attribute_pattern(type) def item_attribute_pattern(type)
@item_attribute_pattern ||= %r{\A#{item_url_pattern(type)}([^/]+)/}i @item_attribute_pattern ||= %r{#{item_url_pattern(type)}([^/]+)/}i
end end
# @param [ String ] type # @param [ String ] type
@@ -59,7 +61,7 @@ module WPScan
item_dir = type == 'plugins' ? target.plugins_dir : target.content_dir item_dir = type == 'plugins' ? target.plugins_dir : target.content_dir
item_url = type == 'plugins' ? target.plugins_url : target.content_url item_url = type == 'plugins' ? target.plugins_url : target.content_url
url = /#{item_url.gsub(/\A(?:http|https)/i, 'https?').gsub('/', '\\\\\?\/')}/i url = /#{item_url.gsub(/\A(?:https?)/i, 'https?').gsub('/', '\\\\\?\/')}/i
item_dir = %r{(?:#{url}|\\?\/#{item_dir.gsub('/', '\\\\\?\/')}\\?/)}i item_dir = %r{(?:#{url}|\\?\/#{item_dir.gsub('/', '\\\\\?\/')}\\?/)}i
type == 'plugins' ? item_dir : %r{#{item_dir}#{type}\\?\/}i type == 'plugins' ? item_dir : %r{#{item_dir}#{type}\\?\/}i

View File

@@ -28,7 +28,7 @@ module WPScan
end end
def passive_urls_xpath def passive_urls_xpath
'//a[contains(@href, "rdf")]/@href' '//a[contains(@href, "/rdf")]/@href'
end end
def aggressive_urls(_opts = {}) def aggressive_urls(_opts = {})

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
@@ -94,7 +101,7 @@ module WPScan
# #
# @return [ String ] # @return [ String ]
def parse_style_tag(body, tag) def parse_style_tag(body, tag)
value = body[/^\s*#{Regexp.escape(tag)}:[\t ]*([^\r\n]+)/i, 1] value = body[/#{Regexp.escape(tag)}:[\t ]*([^\r\n\*]+)/i, 1]
value && !value.strip.empty? ? value.strip : nil value && !value.strip.empty? ? value.strip : nil
end end

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_uris, :head_or_get_params, to: :blog delegate :homepage_res, :error_404_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
@@ -22,7 +23,7 @@ module WPScan
# @option opts [ Hash ] :version_detection The options to use when looking for the version # @option opts [ Hash ] :version_detection The options to use when looking for the version
# @option opts [ String ] :url The URL of the item # @option opts [ String ] :url The URL of the item
def initialize(slug, blog, opts = {}) def initialize(slug, blog, opts = {})
@slug = URI.decode(slug) @slug = Addressable::URI.unencode(slug)
@blog = blog @blog = blog
@uri = Addressable::URI.parse(opts[:url]) if opts[:url] @uri = Addressable::URI.parse(opts[:url]) if opts[:url]
@@ -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 ]
@@ -82,11 +83,6 @@ module WPScan
end end
end end
# URI.encode is preferered over Addressable::URI.encode as it will encode
# leading # character:
# URI.encode('#t#') => %23t%23
# Addressable::URI.encode('#t#') => #t%23
#
# @param [ String ] path Optional path to merge with the uri # @param [ String ] path Optional path to merge with the uri
# #
# @return [ String ] # @return [ String ]
@@ -94,7 +90,7 @@ module WPScan
return unless @uri return unless @uri
return @uri.to_s unless path return @uri.to_s unless path
@uri.join(URI.encode(path)).to_s @uri.join(Addressable::URI.encode(path)).to_s
end end
# @return [ Boolean ] # @return [ Boolean ]
@@ -117,7 +113,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 +122,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
# #
@@ -161,7 +161,7 @@ module WPScan
# @return [ Typhoeus::Response ] # @return [ Typhoeus::Response ]
def head_and_get(path, codes = [200], params = {}) def head_and_get(path, codes = [200], params = {})
final_path = +@path_from_blog final_path = +@path_from_blog
final_path << URI.encode(path) unless path.nil? final_path << path unless path.nil?
blog.head_and_get(final_path, codes, params) blog.head_and_get(final_path, codes, params)
end end

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

@@ -1,14 +1,14 @@
_______________________________________________________________ _______________________________________________________________
__ _______ _____ __ _______ _____
\ \ / / __ \ / ____| \ \ / / __ \ / ____|
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ® \ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \ \ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
\ /\ / | | ____) | (__| (_| | | | | \ /\ / | | ____) | (__| (_| | | | |
\/ \/ |_| |_____/ \___|\__,_|_| |_| \/ \/ |_| |_____/ \___|\__,_|_| |_|
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

@@ -1,4 +1,4 @@
| Detected By: <%= @item.found_by %> | Found By: <%= @item.found_by %>
<% @item.interesting_entries.each do |entry| -%> <% @item.interesting_entries.each do |entry| -%>
| - <%= entry %> | - <%= entry %>
<% 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/users/sign_up
<% end -%>

View File

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

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/users/sign_up"
<% 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,7 +13,8 @@ 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'
@@ -38,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

@@ -7,7 +7,7 @@ module WPScan
# @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 config_backups.txt db_exports.txt timthumbs-v3.txt config_backups.txt db_exports.txt
dynamic_finders.yml wp_fingerprints.json LICENSE dynamic_finders.yml LICENSE sponsor.txt
].freeze ].freeze
OLD_FILES = %w[wordpress.db user-agents.txt 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

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

@@ -0,0 +1,80 @@
# 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
return {} if path.end_with?('/latest') # Remove this when api/v4 is up
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

@@ -44,19 +44,27 @@ module WPScan
# #
# @param [ Typhoeus::Response ] response # @param [ Typhoeus::Response ] response
# @param [ Hash ] opts # @param [ Hash ] opts
# @return [ Mixed ] # @return [ Mixed: nil, Object, Array ]
def find(_response, _opts = {}) def find(_response, _opts = {})
raise NoMethodError raise NoMethodError
end end
# @param [ Hash ] opts # @param [ Hash ] opts
# @return [ Mixed ] See #find
def passive(opts = {}) def passive(opts = {})
return if self.class::PATH return if self.class::PATH
find(target.homepage_res, opts) homepage_result = find(target.homepage_res, opts)
if homepage_result
return homepage_result unless homepage_result.is_a?(Array) && homepage_result.empty?
end
find(target.error_404_res, opts)
end end
# @param [ Hash ] opts # @param [ Hash ] opts
# @return [ Mixed ] See #find
def aggressive(opts = {}) def aggressive(opts = {})
return unless self.class::PATH return unless self.class::PATH

View File

@@ -4,7 +4,7 @@ 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 < Finders::DynamicFinder::Version::Finder class BodyPattern < Finders::DynamicFinder::Version::Finder
# @return [ Hash ] # @return [ Hash ]
@@ -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

@@ -31,9 +31,14 @@ module WPScan
passive_configs.each do |slug, configs| passive_configs.each do |slug, configs|
configs.each do |klass, config| configs.each do |klass, config|
item = process_response(opts, target.homepage_res, slug, klass, config) [target.homepage_res, target.error_404_res].each do |page_res|
item = process_response(opts, page_res, slug, klass, config)
found << item if item.is_a?(Model::WpItem) if item.is_a?(Model::WpItem)
found << item
break # No need to check the other page if detected in the current
end
end
end end
end end

View File

@@ -37,6 +37,8 @@ module WPScan
end end
end end
# This one has been disabled from the DF.yml as it was causing FPs when a plugin had numerous
# files matching a known WP version.
class WpItemQueryParameter < QueryParameter class WpItemQueryParameter < QueryParameter
def xpath def xpath
@xpath ||= @xpath ||=

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

View File

@@ -11,7 +11,9 @@ module WPScan
module WordPress module WordPress
include CMSScanner::Target::Platform::PHP include CMSScanner::Target::Platform::PHP
WORDPRESS_PATTERN = %r{/(?:(?:wp-content/(?:themes|(?:mu\-)?plugins|uploads))|wp-includes)/}i.freeze WORDPRESS_PATTERN = %r{/(?:(?:wp-content/(?:themes|(?:mu\-)?plugins|uploads))|wp-includes)/}i.freeze
WP_JSON_OEMBED_PATTERN = %r{/wp\-json/oembed/}i.freeze
WP_ADMIN_AJAX_PATTERN = %r{\\?/wp\-admin\\?/admin\-ajax\.php}i.freeze
# These methods are used in the associated interesting_findings finders # These methods are used in the associated interesting_findings finders
# to keep the boolean state of the finding rather than re-check the whole thing again # to keep the boolean state of the finding rather than re-check the whole thing again
@@ -22,22 +24,16 @@ module WPScan
# @param [ Symbol ] detection_mode # @param [ Symbol ] detection_mode
# #
# @return [ Boolean ] # @return [ Boolean ] Whether or not the target is running WordPress
def wordpress?(detection_mode) def wordpress?(detection_mode)
in_scope_uris(homepage_res) do |uri| [homepage_res, error_404_res].each do |page_res|
return true if uri.path.match(WORDPRESS_PATTERN) return true if wordpress_from_meta_comments_or_scripts?(page_res)
end end
homepage_res.html.css('meta[name="generator"]').each do |node|
return true if node['content'] =~ /wordpress/i
end
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_uris(Browser.get_and_follow_location(url(path))).each do |uri| return true if in_scope_uris(Browser.get_and_follow_location(url(path))).any? do |uri|
return true if uri.path.match(WORDPRESS_PATTERN) WORDPRESS_PATTERN.match?(uri.path)
end end
end end
end end
@@ -45,6 +41,26 @@ module WPScan
false false
end end
# @param [ Typhoeus::Response ] response
# @return [ Boolean ]
def wordpress_from_meta_comments_or_scripts?(response)
in_scope_uris(response) do |uri|
return true if WORDPRESS_PATTERN.match?(uri.path) || WP_JSON_OEMBED_PATTERN.match?(uri.path)
end
return true if response.html.css('meta[name="generator"]').any? do |node|
/wordpress/i.match?(node['content'])
end
return true unless comments_from_page(/wordpress/i, response).empty?
return true if response.html.xpath('//script[not(@src)]').any? do |node|
WP_ADMIN_AJAX_PATTERN.match?(node.text)
end
false
end
COOKIE_PATTERNS = { COOKIE_PATTERNS = {
'vjs' => /createCookie\('vjs','(?<c_value>\d+)',\d+\);/i 'vjs' => /createCookie\('vjs','(?<c_value>\d+)',\d+\);/i
}.freeze }.freeze
@@ -82,7 +98,7 @@ module WPScan
def wordpress_hosted? def wordpress_hosted?
return true if /\.wordpress\.com$/i.match?(uri.host) return true if /\.wordpress\.com$/i.match?(uri.host)
unless content_dir(:passive) unless content_dir
pattern = %r{https?://s\d\.wp\.com#{WORDPRESS_PATTERN}}i.freeze pattern = %r{https?://s\d\.wp\.com#{WORDPRESS_PATTERN}}i.freeze
uris_from_page(homepage_res) do |uri| uris_from_page(homepage_res) do |uri|
@@ -109,6 +125,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,25 +13,24 @@ 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(detection_mode = :mixed) def content_dir
unless @content_dir unless @content_dir
# scope_url_pattern is from CMSScanner::Target # scope_url_pattern is from CMSScanner::Target
pattern = %r{#{scope_url_pattern}([\w\s\-/]+)\\?/(?:themes|plugins|uploads|cache)\\?/}i pattern = %r{#{scope_url_pattern}([\w\s\-/]+?)\\?/(?:themes|plugins|uploads|cache)\\?/}i
in_scope_uris(homepage_res) do |uri| [homepage_res, error_404_res].each do |page_res|
return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern) in_scope_uris(page_res, '//link/@href|//script/@src|//img/@src') do |uri|
return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
end
# 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, page_res) do |match|
return @content_dir = match[1]
end
end end
# Checks for the pattern in raw JS code, as well as @content attributes of meta tags return @content_dir = 'wp-content' if default_content_dir_exists?
xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, homepage_res) do |match|
return @content_dir = match[1]
end
unless detection_mode == :passive
return @content_dir = 'wp-content' if default_content_dir_exists?
end
end end
@content_dir @content_dir
@@ -72,7 +71,7 @@ module WPScan
# #
# @return [ String ] # @return [ String ]
def plugin_url(slug) def plugin_url(slug)
plugins_uri.join("#{URI.encode(slug)}/").to_s plugins_uri.join("#{Addressable::URI.encode(slug)}/").to_s
end end
# @return [ String ] # @return [ String ]
@@ -94,25 +93,26 @@ module WPScan
# #
# @return [ String ] # @return [ String ]
def theme_url(slug) def theme_url(slug)
themes_uri.join("#{URI.encode(slug)}/").to_s themes_uri.join("#{Addressable::URI.encode(slug)}/").to_s
end end
# @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?
# url_pattern is from CMSScanner::Target
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
in_scope_uris(homepage_res) do |uri| # url_pattern is from CMSScanner::Target
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
[homepage_res, error_404_res].each do |page_res|
in_scope_uris(page_res) do |uri|
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern) return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
end end
@sub_dir = false
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

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.5' VERSION = '3.7.7'
end end

View File

@@ -166,6 +166,7 @@ describe WPScan::Controller::Core 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(true) expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
expect(core.target).to receive(:wordpress_hosted?).and_return(false)
end end
it 'calls the formatter when started and finished to update the db' do it 'calls the formatter when started and finished to update the db' do
@@ -174,56 +175,6 @@ describe WPScan::Controller::Core do
end end
end end
context 'when a redirect occurs' do
before do
stub_request(:any, target_url)
expect(core.target).to receive(:homepage_res)
.at_least(1)
.and_return(Typhoeus::Response.new(effective_url: redirection, body: ''))
end
context 'to the wp-admin/install.php' do
let(:redirection) { "#{target_url}wp-admin/install.php" }
it 'calls the formatter with the correct parameters and exit' do
expect(core.formatter).to receive(:output)
.with('not_fully_configured', hash_including(url: redirection), 'core').ordered
# TODO: Would be cool to be able to test the exit code
expect { core.before_scan }.to raise_error(SystemExit)
end
end
context 'to something else' do
let(:redirection) { 'http://g.com/' }
it 'raises an error' do
expect { core.before_scan }.to raise_error(CMSScanner::Error::HTTPRedirect)
end
end
context 'to another path with the wp-admin/install.php in the query' do
let(:redirection) { "#{target_url}index.php?a=/wp-admin/install.php" }
context 'when wordpress' do
it 'does not raise an error' do
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
expect { core.before_scan }.to_not raise_error
end
end
context 'when not wordpress' 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
end
end
context 'when hosted on wordpress.com' do context 'when hosted on wordpress.com' do
let(:target_url) { 'http://ex.wordpress.com' } let(:target_url) { 'http://ex.wordpress.com' }
@@ -234,52 +185,106 @@ describe WPScan::Controller::Core do
end end
end end
context 'when wordpress' do context 'when not hosted on wordpress.com' do
before do before { allow(core.target).to receive(:wordpress_hosted?).and_return(false) }
expect(core).to receive(:load_server_module)
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
end
it 'does not raise any error' do context 'when a redirect occurs' do
expect { core.before_scan }.to_not raise_error before do
end stub_request(:any, target_url)
end
context 'when not wordpress' do expect(core.target).to receive(:homepage_res)
before do .at_least(1)
expect(core).to receive(:load_server_module) .and_return(Typhoeus::Response.new(effective_url: redirection, body: ''))
end end
context 'when no --force' do context 'to the wp-admin/install.php' do
before { expect(core.target).to receive(:maybe_add_cookies) } let(:redirection) { "#{target_url}wp-admin/install.php" }
context 'when no cookies added or still not wordpress after being added' do it 'calls the formatter with the correct parameters and exit' do
it 'raises an error' do expect(core.formatter).to receive(:output)
expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false) .with('not_fully_configured', hash_including(url: redirection), 'core').ordered
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress) # TODO: Would be cool to be able to test the exit code
expect { core.before_scan }.to raise_error(SystemExit)
end end
end end
context 'when the added cookies solved it' do context 'to something else' do
it 'does not raise an error' do let(:redirection) { 'http://g.com/' }
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false).ordered
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true).ordered it 'raises an error' do
expect { core.before_scan }.to raise_error(CMSScanner::Error::HTTPRedirect)
end
end
context 'to another path with the wp-admin/install.php in the query' do
let(:redirection) { "#{target_url}index.php?a=/wp-admin/install.php" }
context 'when wordpress' do
it 'does not raise an error' do
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
expect { core.before_scan }.to_not raise_error
end
end
context 'when not wordpress' 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
end
end
context 'when wordpress' do
before do
expect(core).to receive(:load_server_module)
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
end
it 'does not raise any error' do
expect { core.before_scan }.to_not raise_error
end
end
context 'when not wordpress' do
before do
expect(core).to receive(:load_server_module)
end
context 'when no --force' do
before { expect(core.target).to receive(:maybe_add_cookies) }
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
context 'when --force' do
let(:cli_args) { "#{super()} --force" }
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
end end
context 'when --force' do
let(:cli_args) { "#{super()} --force" }
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
end
end
end end
end end
end end

View File

@@ -20,7 +20,7 @@ describe WPScan::Controller::CustomDirectories do
describe '#before_scan' do describe '#before_scan' do
context 'when the content_dir is not found and not supplied' do context 'when the content_dir is not found and not supplied' do
before { expect(controller.target).to receive(:content_dir).with(:mixed) } before { expect(controller.target).to receive(:content_dir).and_return(nil) }
it 'raises an exception' do it 'raises an exception' do
expect { controller.before_scan }.to raise_error(WPScan::Error::WpContentDirNotDetected) expect { controller.before_scan }.to raise_error(WPScan::Error::WpContentDirNotDetected)

View File

@@ -70,8 +70,8 @@ describe WPScan::Controller::Enumeration do
it 'contains the correct options' do it 'contains the correct options' do
expect(controller.cli_options.map(&:to_sym)).to eql( expect(controller.cli_options.map(&:to_sym)).to eql(
%i[enumerate exclude_content_based %i[enumerate exclude_content_based
plugins_list plugins_detection plugins_version_all plugins_version_detection plugins_list plugins_detection plugins_version_all plugins_version_detection plugins_threshold
themes_list themes_detection themes_version_all themes_version_detection themes_list themes_detection themes_version_all themes_version_detection themes_threshold
timthumbs_list timthumbs_detection timthumbs_list timthumbs_detection
config_backups_list config_backups_detection config_backups_list config_backups_detection
db_exports_list db_exports_detection db_exports_list db_exports_detection
@@ -102,15 +102,6 @@ describe WPScan::Controller::Enumeration do
end end
end end
describe '#before_scan' do
it 'creates the Dynamic Finders' do
expect(WPScan::DB::DynamicFinders::Plugin).to receive(:create_versions_finders)
expect(WPScan::DB::DynamicFinders::Theme).to receive(:create_versions_finders)
controller.before_scan
end
end
describe '#run' do describe '#run' do
context 'when no :enumerate' do context 'when no :enumerate' do
before do before do

View File

@@ -0,0 +1,93 @@
# frozen_string_literal: true
describe WPScan::Controller::VulnApi do
subject(:controller) { described_class.new }
let(:target_url) { 'http://ex.lo/' }
let(:cli_args) { "--url #{target_url}" }
before do
WPScan::ParsedCli.options = rspec_parsed_options(cli_args)
end
describe '#cli_options' do
its(:cli_options) { should_not be_empty }
its(:cli_options) { should be_a Array }
it 'contains to correct options' do
expect(controller.cli_options.map(&:to_sym)).to eq %i[api_token]
end
end
describe '#before_scan' do
context 'when no --api-token provided' do
its(:before_scan) { should be nil }
end
context 'when --api-token given' do
let(:cli_args) { "#{super()} --api-token token" }
context 'when the token is invalid' do
before { expect(WPScan::DB::VulnApi).to receive(:status).and_return('error' => 'HTTP Token: Access denied.') }
it 'raise an InvalidApiToken error' do
expect { controller.before_scan }.to raise_error(WPScan::Error::InvalidApiToken)
end
end
context 'when the token is valid' do
context 'when the limit has been reached' do
before do
expect(WPScan::DB::VulnApi)
.to receive(:status)
.and_return('success' => true, 'plan' => 'free', 'requests_remaining' => 0)
end
it 'raises an ApiLimitReached error' do
expect { controller.before_scan }.to raise_error(WPScan::Error::ApiLimitReached)
end
end
context 'when a HTTP error, like a timeout' do
before do
expect(WPScan::DB::VulnApi)
.to receive(:status)
.and_return(
'http_error' => WPScan::Error::HTTP.new(
Typhoeus::Response.new(effective_url: 'mock-url', return_code: 28)
)
)
end
it 'raises an HTTP error' do
expect { controller.before_scan }
.to raise_error(WPScan::Error::HTTP, 'HTTP Error: mock-url (Timeout was reached)')
end
end
context 'when the token is valid and no HTTP error' do
before do
expect(WPScan::DB::VulnApi)
.to receive(:status)
.and_return('success' => true, 'plan' => 'free', 'requests_remaining' => requests)
end
context 'when limited requests' do
let(:requests) { 100 }
it 'does not raise an error' do
expect { controller.before_scan }.to_not raise_error
end
context 'when unlimited requests' do
let(:requests) { 'Unlimited' }
it 'does not raise an error' do
expect { controller.before_scan }.to_not raise_error
end
end
end
end
end
end
end
end

View File

@@ -9,7 +9,7 @@ describe WPScan::Finders::DbExports::KnownLocations do
describe '#potential_urls' do describe '#potential_urls' do
before do before do
expect(target).to receive(:sub_dir).at_least(1).and_return(false) allow(target).to receive(:sub_dir).and_return(false)
end end
it 'replace {domain_name} by its value' do it 'replace {domain_name} by its value' do
@@ -22,11 +22,45 @@ describe WPScan::Finders::DbExports::KnownLocations do
http://ex.lo/aa/backups/db_backup.sql http://ex.lo/aa/backups/db_backup.sql
] ]
end end
%w[dev poc www].each do |sub_domain|
context "when #{sub_domain} sub-domain" do
let(:url) { "https://#{sub_domain}.domain.tld" }
it 'replace {domain_name} by its correct value' do
expect(finder.potential_urls(opts).keys).to include "#{url}/domain.sql"
end
end
end
context 'when multi-level tlds' do
let(:url) { 'https://something.com.tr' }
it 'replace {domain_name} by its correct value' do
expect(finder.potential_urls(opts).keys).to include 'https://something.com.tr/something.sql'
end
end
context 'when multi-level tlds and sub-domain' do
let(:url) { 'https://dev.something.com.tr' }
it 'replace {domain_name} by its correct value' do
expect(finder.potential_urls(opts).keys).to include 'https://dev.something.com.tr/something.sql'
end
end
context 'when some weird stuff' do
let(:url) { 'https://098f6bcd4621d373cade4e832627b4f6.aa-bb-ccc-dd.domain-test.com' }
it 'replace {domain_name} by its correct value' do
expect(finder.potential_urls(opts).keys).to include "#{url}/domain-test.sql"
end
end
end end
describe '#aggressive' do describe '#aggressive' do
before do before do
expect(target).to receive(:sub_dir).at_least(1).and_return(false) allow(target).to receive(:sub_dir).and_return(false)
expect(target).to receive(:head_or_get_params).and_return(method: :head) expect(target).to receive(:head_or_get_params).and_return(method: :head)
finder.potential_urls(opts).each_key do |url| finder.potential_urls(opts).each_key do |url|

View File

@@ -0,0 +1,11 @@
# frozen_string_literal: true
describe WPScan::Finders::MainTheme::CssStyleIn404Page do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style_in_404_page') }
# This stuff is just a child class of CssStyleInHomepage (using the error_404_res rather than homepage_res)
# which already has a spec
end

View File

@@ -1,10 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
describe WPScan::Finders::MainTheme::CssStyle do describe WPScan::Finders::MainTheme::CssStyleInHomepage do
subject(:finder) { described_class.new(target) } subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) } let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://wp.lab/' } let(:url) { 'http://wp.lab/' }
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style') } let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style_in_homepage') }
describe '#passive' do describe '#passive' do
after do after do
@@ -33,7 +33,7 @@ describe WPScan::Finders::MainTheme::CssStyle do
@expected = WPScan::Model::Theme.new( @expected = WPScan::Model::Theme.new(
'twentyfifteen', 'twentyfifteen',
target, target,
found_by: 'Css Style (Passive Detection)', found_by: 'Css Style In Homepage (Passive Detection)',
confidence: 70, confidence: 70,
style_url: 'http://wp.lab/wp-content/themes/twentyfifteen/style.css?ver=4.1.1' style_url: 'http://wp.lab/wp-content/themes/twentyfifteen/style.css?ver=4.1.1'
) )
@@ -47,7 +47,7 @@ describe WPScan::Finders::MainTheme::CssStyle do
@expected = WPScan::Model::Theme.new( @expected = WPScan::Model::Theme.new(
'custom', 'custom',
target, target,
found_by: 'Css Style (Passive Detection)', found_by: 'Css Style In Homepage (Passive Detection)',
confidence: 70, confidence: 70,
style_url: 'http://wp.lab/wp-content/themes/custom/style.css' style_url: 'http://wp.lab/wp-content/themes/custom/style.css'
) )

View File

@@ -0,0 +1,11 @@
# frozen_string_literal: true
describe WPScan::Finders::MainTheme::UrlsIn404Page do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'urls_in_404_page') }
# This stuff is just a child class of URLsInHomepage (using the error_404_res rather than homepage_res)
# which already has a spec
end

View File

@@ -6,7 +6,8 @@ describe WPScan::Finders::MainTheme::UrlsInHomepage do
let(:url) { 'http://wp.lab/' } let(:url) { 'http://wp.lab/' }
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'urls_in_homepage') } let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'urls_in_homepage') }
it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do it_behaves_like 'App::Finders::WpItems::UrlsInPage' do
let(:page_url) { url }
let(:type) { 'themes' } let(:type) { 'themes' }
let(:uniq_links) { false } let(:uniq_links) { false }
let(:uniq_codes) { false } let(:uniq_codes) { false }
@@ -18,6 +19,8 @@ describe WPScan::Finders::MainTheme::UrlsInHomepage do
before do before do
stub_request(:get, /.*.css/) stub_request(:get, /.*.css/)
stub_request(:get, target.url).to_return(body: File.read(fixtures.join('found.html'))) stub_request(:get, target.url).to_return(body: File.read(fixtures.join('found.html')))
allow(target).to receive(:content_dir).and_return('wp-content')
end end
it 'returns the expected Themes' do it 'returns the expected Themes' do

View File

@@ -7,32 +7,50 @@ describe WPScan::Finders::MainTheme::WooFrameworkMetaGenerator do
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'woo_framework_meta_generator') } let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'woo_framework_meta_generator') }
describe '#passive' do describe '#passive' do
after do before do
stub_request(:get, url).to_return(body: File.read(fixtures.join(@file))) stub_request(:get, url).to_return(body: File.read(fixtures.join(homepage_fixture)))
stub_request(:get, ERROR_404_URL_PATTERN).to_return(body: File.read(fixtures.join(error_404_fixture)))
expect(finder.passive).to eql @expected
end end
context 'when no Woo generator' do context 'when no Woo generator' do
let(:homepage_fixture) { 'no_woo_generator.html' }
let(:error_404_fixture) { 'no_woo_generator.html' }
it 'returns nil' do it 'returns nil' do
@file = 'no_woo_generator.html' expect(finder.passive).to eql nil
@expected = nil
end end
end end
context 'when Woo generator' do context 'when Woo generator' do
before do before do
expect(target).to receive(:content_dir).at_least(1).and_return('wp-content') allow(target).to receive(:content_dir).and_return('wp-content')
stub_request(:get, "#{url}wp-content/themes/Merchant/style.css") stub_request(:get, "#{url}wp-content/themes/Merchant/style.css")
end end
it 'returns the expected theme' do context 'from the homepage' do
@file = 'woo_generator.html' let(:homepage_fixture) { 'woo_generator.html' }
@expected = WPScan::Model::Theme.new( let(:error_404_fixture) { 'no_woo_generator.html' }
'Merchant', target,
found_by: 'Woo Framework Meta Generator (Passive Detection)', it 'returns the expected theme' do
confidence: 80 expect(finder.passive).to eql WPScan::Model::Theme.new(
) 'Merchant', target,
found_by: 'Woo Framework Meta Generator (Passive Detection)',
confidence: 80
)
end
end
context 'from the 404 page' do
let(:homepage_fixture) { 'no_woo_generator.html' }
let(:error_404_fixture) { 'woo_generator.html' }
it 'returns the expected theme' do
expect(finder.passive).to eql WPScan::Model::Theme.new(
'Merchant', target,
found_by: 'Woo Framework Meta Generator (Passive Detection)',
confidence: 80
)
end
end end
end end
end end

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