Compare commits

...

260 Commits

Author SHA1 Message Date
erwanlr
8dab57b59c Bumps version 2020-10-09 12:30:57 +02:00
Erwan
7a00cd8db1 Merge pull request #1550 from d10r/master
fixed typo in link (wpscam.com)
2020-10-09 11:44:39 +02:00
Didi
daa0915bca fixed typo in link (wpscam.com) 2020-10-09 11:38:54 +02:00
erwanlr
ca6b6a30d8 Updates Rubocop dep 2020-10-09 11:23:17 +02:00
erwanlr
09f2640879 Adds Dfs 2020-10-09 11:18:40 +02:00
erwanlr
f61c55b350 Updates DB URLs 2020-10-08 19:29:57 +02:00
erwanlr
78d0c2540c Adds DFs 2020-10-02 12:26:58 +02:00
Erwan
1d0426e816 Merge pull request #1544 from wpscanteam/dependabot/bundler/rubocop-tw-0.92.0
Update rubocop requirement from ~> 0.91.0 to ~> 0.92.0
2020-09-28 08:24:49 +02:00
dependabot-preview[bot]
103a4049c8 Update rubocop requirement from ~> 0.91.0 to ~> 0.92.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.91.0...v0.92.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-28 05:23:28 +00:00
erwanlr
cbcb1dcb33 Adds Dfs 2020-09-27 11:55:30 +02:00
erwanlr
9c36293382 Adds DFs 2020-09-19 10:39:54 +02:00
erwanlr
2fb36dc425 Adds DF 2020-09-18 15:40:29 +02:00
erwanlr
c717ba5a71 Updates rubocop 2020-09-18 15:15:21 +02:00
Christian Mehlmauer
7572518e3b add label to dockerfile 2020-09-17 08:10:20 +02:00
Erwan
f670133a82 Merge pull request #1541 from wpscanteam/dependabot/bundler/webmock-tw-3.9.0
Update webmock requirement from ~> 3.8.0 to ~> 3.9.0
2020-09-14 08:55:07 +02:00
dependabot-preview[bot]
a6bbf41e82 Update webmock requirement from ~> 3.8.0 to ~> 3.9.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.8.0...v3.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-14 05:23:55 +00:00
erwanlr
622c16932a Adds DFs 2020-09-12 12:21:16 +02:00
erwanlr
5fd7e0ed22 Bumps version 2020-09-10 14:34:44 +02:00
erwanlr
d9f6c71015 Adds specs 2020-09-10 13:41:01 +02:00
Erwan
61a3106b3b Merge pull request #1540 from dwisiswant0/installer-log-regex-update
Update regex for WP Duplicator plugin installer-log
2020-09-10 13:22:15 +02:00
Dwi Siswanto
20eb2d825d 🔨 Update regex 2020-09-10 17:30:36 +07:00
Dwi Siswanto
906557d2ec 🔨 Update regex for WP Duplicator plugin installer-log 2020-09-10 04:38:19 +07:00
erwanlr
c1e278ea80 Updates rubocop-perf 2020-09-07 09:19:13 +02:00
erwanlr
e2d616a53f Adds DFs 2020-09-05 11:14:47 +02:00
erwanlr
c6802ccdd2 Updates rubocop 2020-09-03 11:05:36 +02:00
erwanlr
abd50fd037 Adds DFs 2020-08-29 12:02:20 +02:00
erwanlr
4515be53b4 Adds Dfs 2020-08-22 17:51:39 +02:00
Erwan
920a25bb25 Merge pull request #1536 from wpscanteam/dependabot/bundler/simplecov-tw-0.19.0
Update simplecov requirement from ~> 0.18.2 to ~> 0.19.0
2020-08-17 09:10:44 +02:00
dependabot-preview[bot]
648dd05069 Update simplecov requirement from ~> 0.18.2 to ~> 0.19.0
Updates the requirements on [simplecov](https://github.com/simplecov-ruby/simplecov) to permit the latest version.
- [Release notes](https://github.com/simplecov-ruby/simplecov/releases)
- [Changelog](https://github.com/simplecov-ruby/simplecov/blob/main/CHANGELOG.md)
- [Commits](https://github.com/simplecov-ruby/simplecov/compare/v0.18.2...v0.19.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-17 05:57:47 +00:00
erwanlr
713edcecca Adds DFs 2020-08-15 12:02:27 +02:00
erwanlr
ac16a951c5 Bumps version 2020-08-08 14:21:50 +02:00
erwanlr
1043bcb267 Adds Dfs 2020-08-08 13:25:15 +02:00
erwanlr
22979a1a77 Adds DFs 2020-08-07 09:39:50 +02:00
erwanlr
3039d2e7eb More rubocop fixes 2020-08-06 12:47:00 +02:00
erwanlr
557dee2d8c Updates rubocop 2020-08-06 11:43:26 +02:00
erwanlr
a506adcb64 Fixes #1529 2020-08-06 10:51:08 +02:00
erwanlr
3bfb120646 Adds DF for monarch - Ref #1527 2020-08-04 14:42:50 +02:00
erwanlr
43e613aa52 Updates Gravityforms detection - Ref #1526 2020-08-03 12:33:14 +02:00
erwanlr
0d930ed605 Adds Dfs 2020-08-01 10:45:42 +02:00
erwanlr
2014f1e4b3 Bumps version 2020-07-25 17:13:29 +02:00
erwanlr
4889d17e0a Ref #1510 2020-07-25 16:24:59 +02:00
erwanlr
494d31215d Ref #1510 2020-07-25 16:21:03 +02:00
erwanlr
582bdea431 Adds DFs 2020-07-25 11:51:59 +02:00
erwanlr
ecf7df9c01 Ref #1510 2020-07-24 15:32:41 +02:00
erwanlr
a9760e8817 Ref #1510 2020-07-24 15:26:22 +02:00
erwanlr
b32e990dd4 Bumps version 2020-07-20 15:11:31 +02:00
erwanlr
4320d2436f Fixes #1522 2020-07-20 13:15:54 +02:00
erwanlr
cba6e74b13 Fixes #1520 2020-07-18 13:35:53 +02:00
erwanlr
981bcf5fa2 Adds Dfs 2020-07-18 13:17:37 +02:00
erwanlr
1d79bc37d3 Bumps version 2020-07-17 10:50:15 +02:00
erwanlr
2fae3336ba Removes useless statement 2020-07-16 17:53:59 +02:00
erwanlr
cfb98c5139 Ref #1518 for XMLrPC multicall 2020-07-16 17:50:43 +02:00
erwanlr
b0260327c4 Updates XMLRPC MultiCall attack to reflect use changes. Ref #1518 2020-07-16 14:56:08 +02:00
erwanlr
f65532e347 Uses an enumerator to read wordlist during pwd attack. Fixes #1518 2020-07-16 14:39:09 +02:00
erwanlr
ff574b046c Checks if the wp-login.php is available before attacking it - Fixes #1519 2020-07-16 10:22:45 +02:00
erwanlr
97c995b64c Adds additional check just in case (related to Rubocop offences fixed) 2020-07-15 20:07:47 +02:00
erwanlr
8361ec97e4 Rubocop: Adds new cops by default, fixes offences 2020-07-15 16:45:36 +02:00
erwanlr
7a0bbc0acb Updates cms_scanner dep 2020-07-15 14:24:22 +02:00
Erwan
66f5eca841 Merge pull request #1517 from wpscanteam/dependabot/bundler/rubocop-tw-0.88.0
Update rubocop requirement from ~> 0.87.0 to ~> 0.88.0
2020-07-14 09:11:47 +02:00
dependabot-preview[bot]
b53e6d1888 Update rubocop requirement from ~> 0.87.0 to ~> 0.88.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.87.0...v0.88.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-14 05:38:06 +00:00
erwanlr
4b68fa8b60 Adds DFs 2020-07-11 10:19:52 +02:00
Erwan
54770c5a50 Merge pull request #1516 from wpscanteam/dependabot/bundler/rubocop-tw-0.87.0
Update rubocop requirement from ~> 0.86.0 to ~> 0.87.0
2020-07-07 09:34:54 +02:00
dependabot-preview[bot]
39fb2167f7 Update rubocop requirement from ~> 0.86.0 to ~> 0.87.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.86.0...v0.87.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-07 06:44:44 +00:00
Erwan
c33fef9c98 Merge pull request #1515 from wpscanteam/dependabot/bundler/rubocop-performance-tw-1.7.0
Update rubocop-performance requirement from ~> 1.6.0 to ~> 1.7.0
2020-07-07 08:43:34 +02:00
dependabot-preview[bot]
08a1117edf Update rubocop-performance requirement from ~> 1.6.0 to ~> 1.7.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.6.0...v1.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-07 05:40:52 +00:00
erwanlr
e14cbed56e Adds DF 2020-07-04 11:27:49 +02:00
erwanlr
56e2ab16cc Adds DFs 2020-06-27 11:39:00 +02:00
erwanlr
d76d4b70f5 Updates rubocop & fixes offences 2020-06-23 20:30:48 +02:00
erwanlr
e223936a81 Adds Dfs 2020-06-20 12:00:20 +02:00
Christian Mehlmauer
60d067c421 fix docker install 2020-06-14 20:16:14 +02:00
Christian Mehlmauer
4102cf4688 fix #1487 2020-06-14 19:49:58 +02:00
erwanlr
dc977e6630 Adds missing spec files 2020-06-13 22:10:27 +02:00
erwanlr
05deabd775 Adds DF 2020-06-13 21:32:23 +02:00
erwanlr
549ab4aa15 Bumps version 2020-06-08 20:53:15 +02:00
erwanlr
b189c71682 Typo 2020-06-08 20:14:02 +02:00
erwanlr
b909856933 Fixes #1504 2020-06-08 20:13:18 +02:00
erwanlr
5de9084901 Updates deps 2020-06-08 19:59:04 +02:00
erwanlr
384ef0b44c Adds DFs 2020-06-06 10:48:34 +02:00
Erwan
9307772dc3 Merge pull request #1502 from wpscanteam/dependabot/bundler/rubocop-tw-0.85.0
Update rubocop requirement from ~> 0.84.0 to ~> 0.85.0
2020-06-02 08:26:24 +02:00
dependabot-preview[bot]
730c71d103 Update rubocop requirement from ~> 0.84.0 to ~> 0.85.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.84.0...v0.85.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-02 05:43:24 +00:00
erwanlr
5c710b96f5 Adds Dfs 2020-05-30 11:36:59 +02:00
erwanlr
fe63d0eadf Adds DFs 2020-05-29 10:05:59 +02:00
erwanlr
a6ca95159a Adds DFs 2020-05-23 11:13:57 +02:00
erwanlr
677d32fef5 Updates rubocop-performance and config 2020-05-23 09:17:41 +02:00
Erwan
14abd05969 Merge pull request #1498 from wpscanteam/dependabot/bundler/rubocop-tw-0.84.0
Update rubocop requirement from ~> 0.83.0 to ~> 0.84.0
2020-05-22 08:39:17 +02:00
dependabot-preview[bot]
2e680be34f Update rubocop requirement from ~> 0.83.0 to ~> 0.84.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.83.0...v0.84.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-22 05:32:38 +00:00
erwanlr
fe29942bf4 Adds Dfs 2020-05-16 15:30:47 +02:00
Erwan
c8fb717ac1 Updates Readme's Install section 2020-05-13 21:30:09 +02:00
Erwan
1ff7fcc913 Merge pull request #1493 from wpscanteam/dependabot/bundler/rubocop-tw-0.83.0
Update rubocop requirement from ~> 0.82.0 to ~> 0.83.0
2020-05-12 10:21:34 +02:00
Erwan
419c32702a Fixes rubocop offenses 2020-05-12 09:32:02 +02:00
dependabot-preview[bot]
9b63714caa Update rubocop requirement from ~> 0.82.0 to ~> 0.83.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.82.0...v0.83.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-12 05:35:24 +00:00
erwanlr
f034233607 Adds DFs 2020-05-09 11:46:23 +02:00
erwanlr
be6fcb51b6 Adds Dfs 2020-05-02 12:44:05 +02:00
Ryan Dewhurst
e49a682f00 Add user documentation URL to readme 2020-05-01 11:35:39 +02:00
Erwan
23ad3141a1 Fixes rubocop offence 2020-04-27 09:31:47 +02:00
Erwan
5347e374e0 Fixes rubocop last offence! 2020-04-27 09:26:26 +02:00
Erwan
1a49a628de Fixes rubocop offences 2020-04-27 09:22:59 +02:00
Erwan
8def256d7e Fixes potential InvalidProgressBar error in Pwd Attack 2020-04-27 09:15:37 +02:00
erwanlr
1cd8e6bad7 Adds Dfs 2020-04-25 17:43:29 +02:00
erwanlr
7a03c0db25 Adds DFs 2020-04-18 10:49:21 +02:00
Erwan
e7e3657d1f Merge pull request #1485 from wpscanteam/dependabot/bundler/rubocop-tw-0.82.0
Update rubocop requirement from ~> 0.81.0 to ~> 0.82.0
2020-04-17 10:03:28 +02:00
dependabot-preview[bot]
734dfcc9bc Update rubocop requirement from ~> 0.81.0 to ~> 0.82.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.81.0...v0.82.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-17 05:28:29 +00:00
erwanlr
b0db15099d Bumps version 2020-04-16 11:02:15 +02:00
erwanlr
6fbd2369ba Merge branch 'master' of github.com:wpscanteam/wpscan 2020-04-16 10:20:09 +02:00
erwanlr
f4a6674eed Better code related to CVSS 2020-04-16 10:06:28 +02:00
Ryan Dewhurst
c0567ad4f5 Change wording to security scanner 2020-04-16 09:37:15 +02:00
erwanlr
f146ee7e9f Adds YT references and CVSS to output 2020-04-15 17:02:44 +02:00
erwanlr
e606f4ce18 Adds YT references in output 2020-04-15 14:46:06 +02:00
Ryan Dewhurst
945b589a58 Update README.md
SEO tweak
2020-04-14 09:44:36 +02:00
Erwan
b18042c4a8 Reverts previous commit. spec folder already excluded via .dockerignore 2020-04-13 16:44:27 +02:00
Erwan
a9ff39104b Removes specs folder from Docker build 2020-04-13 16:21:17 +02:00
erwanlr
f6af6e5880 Bumps version 2020-04-12 19:02:57 +02:00
erwanlr
57c6c2d471 Removes support for Ruby 2.4 as EOL 2020-04-12 18:21:23 +02:00
erwanlr
c362527903 Adds DFs 2020-04-11 13:46:37 +02:00
erwanlr
a7acbd0738 Adds DFs 2020-04-04 12:23:40 +02:00
Erwan
f67192ebce Updates help messages regarding plugins-*-detection - Ref #1472 2020-04-02 12:37:09 +02:00
Erwan
c44fde83e4 Merge pull request #1473 from wpscanteam/dependabot/bundler/rubocop-tw-0.81.0
Update rubocop requirement from ~> 0.80.0 to ~> 0.81.0
2020-04-02 09:33:07 +02:00
dependabot-preview[bot]
50119285ef Update rubocop requirement from ~> 0.80.0 to ~> 0.81.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.80.0...v0.81.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-02 05:29:02 +00:00
Erwan
6216916fed Updates ruby version 2020-04-01 09:55:54 +02:00
Erwan
2952380200 Fixes typo 2020-03-30 20:47:12 +02:00
erwanlr
fb42b82e0d Adds DFs 2020-03-28 13:07:39 +01:00
Erwan
6d381ab88d Display valid credentials identified with the [!] icon - Ref #1470 2020-03-23 09:59:24 +01:00
erwanlr
c5c1de32bc Adds DFs 2020-03-21 16:56:16 +01:00
erwanlr
8077ad9bcd Adds DF - Ref #1469 2020-03-19 20:48:41 +01:00
erwanlr
6f22ba350f Adds DFs 2020-03-14 11:33:08 +01:00
erwanlr
f23d0c0157 Bumps version 2020-03-13 16:58:35 +01:00
erwanlr
a9a38edf24 Remove cache action from build 2020-03-13 16:06:33 +01:00
erwanlr
a5534f1e49 Merge branch 'master' of github.com:wpscanteam/wpscan 2020-03-13 16:04:11 +01:00
erwanlr
1c6469f384 Updates deps 2020-03-13 16:03:59 +01:00
erwanlr
8cfdbc1196 Adds DF 2020-03-13 16:03:21 +01:00
Erwan
88737ca6ea Fixes error with reference when debug log identified 2020-03-13 08:12:35 +01:00
Erwan
45bebc60bd Fixes typo in Readme 2020-03-12 22:25:23 +01:00
Erwan
4f7dec4635 Fixes detection of Users via WP JSON when blog uses basic auth 2020-03-12 13:12:40 +01:00
erwanlr
98739cce5a Adds DF 2020-03-10 20:45:08 +01:00
erwanlr
0bfbfacc27 Fixes #1465 2020-03-10 20:31:42 +01:00
erwanlr
73cd862e83 Bumps version 2020-03-09 20:02:44 +01:00
erwanlr
3305e9b74f Adds DFs 2020-03-09 19:24:43 +01:00
erwanlr
c37ec0e8d0 Updates deps 2020-03-09 18:11:47 +01:00
erwanlr
0b005477c1 Adds additional message when checksums mismatch during an update 2020-03-08 20:48:01 +01:00
erwanlr
a1467f8dac Merge branch 'dfs' 2020-03-07 14:56:23 +01:00
erwanlr
40d2c34347 Adds DFs 2020-03-07 14:56:10 +01:00
Erwan
528270e767 Updates Target#wordpress_from_meta_comments_or_scripts Xpath check 2020-03-06 16:17:40 +01:00
erwanlr
f4a04b2387 Adds DFs 2020-03-03 18:54:41 +01:00
erwanlr
14ed6ae109 Fixes #1461 2020-03-02 15:34:51 +01:00
erwanlr
4fd43694ae Typo 2020-03-02 11:57:37 +01:00
erwanlr
552d731e6a Updates Readme to reflect #1460 2020-03-02 11:56:42 +01:00
erwanlr
49ac3ef528 Supports loading api token from ENV, Fixes #1460 2020-03-02 11:45:50 +01:00
erwanlr
4379313f12 Bumps version 2020-02-29 14:24:53 +01:00
erwanlr
3901949f36 Updates deps 2020-02-29 13:27:52 +01:00
erwanlr
a3d8593fed Adds DFs 2020-02-29 12:28:55 +01:00
erwanlr
7c5baeb9c7 Adds DF 2020-02-26 10:55:06 +01:00
erwanlr
c692db5f85 Adds DFs 2020-02-24 13:29:13 +01:00
erwanlr
9130196ffc Runs rubocop before rspec 2020-02-19 14:38:31 +01:00
erwanlr
dad4a65118 Adds Coveralls GH Action 2020-02-19 14:16:31 +01:00
Erwan
4c34c2feb7 Merge pull request #1457 from wpscanteam/dependabot/bundler/rubocop-tw-0.80.0
Update rubocop requirement from ~> 0.79.0 to ~> 0.80.0
2020-02-19 12:32:39 +01:00
dependabot-preview[bot]
23522f7775 Update rubocop requirement from ~> 0.79.0 to ~> 0.80.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.79.0...v0.80.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-19 05:23:43 +00:00
Christian Mehlmauer
82c61398ba change name 2020-02-17 07:32:20 +01:00
Christian Mehlmauer
02871050a6 change step names 2020-02-17 06:34:39 +01:00
Christian Mehlmauer
7d3b1fea6b ruby-head not supported 2020-02-17 06:32:18 +01:00
Christian Mehlmauer
24917fa2a6 wrong key 2020-02-17 06:29:14 +01:00
Christian Mehlmauer
de3d8e4a23 Use a build matrix 2020-02-17 06:28:09 +01:00
erwanlr
1502845d65 Adds DFs 2020-02-15 13:20:54 +01:00
erwanlr
af3f10f74e Reduces time to detect the sub_dir when a lot of urls 2020-02-13 20:07:25 +00:00
erwanlr
c100372b31 Reduces time to detect non WP sites 2020-02-13 19:15:30 +00:00
erwanlr
72d699b39a Adds more improvements re passive scanning when there are a lot of urls 2020-02-13 15:36:42 +00:00
erwanlr
7d2b8a2a8b Reduces time to detect non WP sites when there are a lof of links in pages 2020-02-12 19:50:00 +00:00
erwanlr
8729c68e22 Removes useless params set 2020-02-11 16:39:01 +00:00
erwanlr
e2d48bedd9 Better fix for #1451, adds target IP address to output, ref #1088 2020-02-11 16:28:59 +00:00
erwanlr
6b241ce9b3 Avoids merging CLI params when calling VulnAPI - Ref #1451 2020-02-11 09:14:14 +00:00
erwanlr
1b68bdb36c Bumps version 2020-02-09 13:31:40 +00:00
erwanlr
fb82538441 Fixes #1451 2020-02-09 12:40:48 +00:00
erwanlr
2709d0869a Adds DFs 2020-02-08 10:28:19 +00:00
erwanlr
343f87bbe7 Adds DFs 2020-02-01 10:02:38 +00:00
erwanlr
ecbfc6004c Adds DFs 2020-01-25 12:28:16 +00:00
erwanlr
c57eecc81b Adds DF 2020-01-23 12:30:27 +00:00
erwanlr
7ea14dc03f Adds DF 2020-01-23 11:15:28 +00:00
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
784 changed files with 328473 additions and 5549 deletions

View File

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

41
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
ruby: [2.5, 2.6, 2.7]
steps:
- name: Checkout code
uses: actions/checkout@v1
- name: Set up Ruby ${{ matrix.ruby }}
uses: actions/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: Install GEMs
run: |
gem install bundler
bundle config force_ruby_platform true
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: rubocop
run: |
bundle exec rubocop
- name: rspec
run: |
bundle exec rspec
- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

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

5
.rspec
View File

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

View File

@@ -1,9 +1,16 @@
require: rubocop-performance
AllCops:
TargetRubyVersion: 2.4
NewCops: enable
TargetRubyVersion: 2.5
Exclude:
- '*.gemspec'
- 'vendor/**/*'
Layout/LineLength:
Max: 120
Lint/ConstantDefinitionInBlock:
Enabled: false
Lint/MissingSuper:
Enabled: false
Lint/UriEscapeUnescape:
Enabled: false
Metrics/AbcSize:
@@ -16,13 +23,13 @@ Metrics/ClassLength:
Exclude:
- 'app/controllers/enumeration/cli_options.rb'
Metrics/CyclomaticComplexity:
Max: 8
Metrics/LineLength:
Max: 120
Max: 10
Metrics/MethodLength:
Max: 20
Exclude:
- 'app/controllers/enumeration/cli_options.rb'
Metrics/PerceivedComplexity:
Max: 11
Style/ClassVars:
Enabled: false
Style/Documentation:

View File

@@ -1 +1 @@
2.6.2
2.7.1

View File

@@ -1,4 +1,19 @@
# frozen_string_literal: true
if ENV['GITHUB_ACTION']
require 'simplecov-lcov'
SimpleCov::Formatter::LcovFormatter.config do |c|
c.single_report_path = 'coverage/lcov.info'
c.report_with_single_file = true
end
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
end
SimpleCov.start do
enable_coverage :branch # Only supported for Ruby >= 2.5
add_filter '/spec/'
add_filter 'helper'
end
end

View File

@@ -1,33 +0,0 @@
language: ruby
sudo: false
cache: bundler
rvm:
- 2.4.1
- 2.4.2
- 2.4.3
- 2.4.4
- 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:
- bundle exec rubocop
- bundle exec rspec
notifications:
email:
- team@wpscan.org

View File

@@ -1,16 +1,16 @@
FROM ruby:2.6.3-alpine AS builder
FROM ruby:2.7.1-alpine AS builder
LABEL maintainer="WPScan Team <team@wpscan.org>"
ARG BUNDLER_ARGS="--jobs=8 --without test development"
RUN echo "gem: --no-ri --no-rdoc" > /etc/gemrc
RUN echo "install: --no-document --no-post-install-message\nupdate: --no-document --no-post-install-message" > /etc/gemrc
COPY . /wpscan
RUN apk add --no-cache git libcurl ruby-dev libffi-dev make gcc musl-dev zlib-dev procps sqlite-dev && \
bundle install --system --clean --no-cache --gemfile=/wpscan/Gemfile $BUNDLER_ARGS && \
# temp fix for https://github.com/bundler/bundler/issues/6680
rm -rf /usr/local/bundle/cache
bundle config force_ruby_platform true && \
bundle config disable_version_check 'true' && \
bundle config without "test development" && \
bundle config path.system 'true' && \
bundle install --gemfile=/wpscan/Gemfile --jobs=8
WORKDIR /wpscan
RUN rake install --trace
@@ -19,8 +19,9 @@ RUN rake install --trace
RUN chmod -R a+r /usr/local/bundle
FROM ruby:2.6.3-alpine
FROM ruby:2.7.1-alpine
LABEL maintainer="WPScan Team <team@wpscan.org>"
LABEL org.opencontainers.image.source https://github.com/wpscanteam/wpscan
RUN adduser -h /wpscan -g WPScan -D wpscan
@@ -38,4 +39,3 @@ USER wpscan
RUN /usr/local/bundle/bin/wpscan --update --verbose
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.
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
Free-use Terms and Conditions;
3. Redistribution

View File

@@ -7,15 +7,15 @@
<h3 align="center">WPScan</h3>
<p align="center">
WordPress Vulnerability Scanner
WordPress Security Scanner
<br>
<br>
<a href="https://wpscan.org/" title="homepage" target="_blank">Homepage</a> - <a href="https://wpscan.io/" title="wpscan.io" target="_blank">WPScan.io</a> - <a href="https://wpvulndb.com/" title="vulnerability database" target="_blank">Vulnerability Database</a> - <a href="https://wordpress.org/plugins/wpscan/" title="wordpress plugin" target="_blank">WordPress Plugin</a>
<a href="https://wpscan.com/" title="homepage" target="_blank">WPScan WordPress Vulnerability Database</a> - <a href="https://wordpress.org/plugins/wpscan/" title="wordpress security plugin" target="_blank">WordPress Security Plugin</a>
</p>
<p align="center">
<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://github.com/wpscanteam/wpscan/actions?query=workflow%3ABuild" target="_blank"><img src="https://github.com/wpscanteam/wpscan/workflows/Build/badge.svg"></a>
<a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
</p>
@@ -31,7 +31,11 @@
- RubyGems - Recommended: latest
- Nokogiri might require packages to be installed via your package manager depending on your OS, see https://nokogiri.org/tutorials/installing_nokogiri.html
### From RubyGems (Recommended)
### In a Pentesting distribution
When using a pentesting distubution (such as Kali Linux), it is recommended to install/update wpscan via the package manager if available.
### From RubyGems
```shell
gem install wpscan
@@ -39,18 +43,6 @@ gem install wpscan
On MacOSX, if a ```Gem::FilePermissionError``` is raised due to the Apple's System Integrity Protection (SIP), either install RVM and install wpscan again, or run ```sudo gem install -n /usr/local/bin wpscan``` (see [#1286](https://github.com/wpscanteam/wpscan/issues/1286))
### From sources (NOT Recommended)
Prerequisites: Git
```shell
git clone https://github.com/wpscanteam/wpscan
cd wpscan/
bundle install && rake install
```
# Updating
You can update the local database by using ```wpscan --update```
@@ -77,6 +69,8 @@ docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u1-1
# Usage
Full user documentation can be found here; https://github.com/wpscanteam/wpscan/wiki/WPScan-User-Documentation
```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.
@@ -88,7 +82,7 @@ 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/).
The WPScan CLI tool uses the [WPScan API](https://wpscan.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 [WPScan](https://wpscan.com/register). 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 [WPScan](https://wpscan.com/).
## Load CLI options from file/s
@@ -130,6 +124,11 @@ cli_options:
api_token: YOUR_API_TOKEN
```
## Load API Token From ENV (since v3.7.10)
The API Token will be automatically loaded from the ENV variable `WPSCAN_API_TOKEN` if present. If the `--api-token` CLI option is also provided, the value from the CLI will be used.
## Enumerating usernames
```shell

View File

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

View File

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

View File

@@ -51,7 +51,7 @@ module WPScan
OptSmartList.new(['--plugins-list LIST', 'List of plugins to enumerate'], advanced: true),
OptChoice.new(
['--plugins-detection MODE',
'Use the supplied mode to enumerate Plugins, instead of the global (--detection-mode) mode.'],
'Use the supplied mode to enumerate Plugins.'],
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :passive
),
OptBoolean.new(
@@ -62,8 +62,7 @@ module WPScan
),
OptChoice.new(
['--plugins-version-detection MODE',
'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
'or --plugins-detection modes.'],
'Use the supplied mode to check plugins\' versions.'],
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
),
OptInteger.new(

View File

@@ -23,27 +23,32 @@ module WPScan
]
end
def run
return unless ParsedCli.passwords
if user_interaction?
output('@info',
msg: "Performing password attack on #{attacker.titleize} against #{users.size} user/s")
end
attack_opts = {
def attack_opts
@attack_opts ||= {
show_progression: user_interaction?,
multicall_max_passwords: ParsedCli.multicall_max_passwords
}
end
def run
return unless ParsedCli.passwords
begin
found = []
attacker.attack(users, passwords(ParsedCli.passwords), attack_opts) do |user|
if user_interaction?
output('@info',
msg: "Performing password attack on #{attacker.titleize} against #{users.size} user/s")
end
attacker.attack(users, ParsedCli.passwords, attack_opts) do |user|
found << user
attacker.progress_bar.log("[SUCCESS] - #{user.username} / #{user.password}")
end
rescue Error::NoLoginInterfaceDetected => e
# TODO: Maybe output that in JSON as well.
output('@notice', msg: e.to_s) if user_interaction?
ensure
output('users', users: found)
end
@@ -65,6 +70,8 @@ module WPScan
case ParsedCli.password_attack
when :wp_login
raise Error::NoLoginInterfaceDetected unless target.login_url
Finders::Passwords::WpLogin.new(target)
when :xmlrpc
raise Error::XMLRPCNotDetected unless xmlrpc
@@ -81,8 +88,8 @@ module WPScan
def xmlrpc_get_users_blogs_enabled?
if xmlrpc&.enabled? &&
xmlrpc.available_methods.include?('wp.getUsersBlogs') &&
xmlrpc.method_call('wp.getUsersBlogs', [SecureRandom.hex[0, 6], SecureRandom.hex[0, 4]])
.run.body !~ /XML\-RPC services are disabled/
!xmlrpc.method_call('wp.getUsersBlogs', [SecureRandom.hex[0, 6], SecureRandom.hex[0, 4]])
.run.body.match?(/>\s*405\s*</)
true
else
@@ -100,8 +107,10 @@ module WPScan
else
Finders::Passwords::XMLRPC.new(xmlrpc)
end
else
elsif target.login_url
Finders::Passwords::WpLogin.new(target)
else
raise Error::NoLoginInterfaceDetected
end
end
@@ -113,15 +122,6 @@ module WPScan
acc << Model::User.new(elem.chomp)
end
end
# @param [ String ] wordlist_path
#
# @return [ Array<String> ]
def passwords(wordlist_path)
@passwords ||= File.open(wordlist_path).reduce([]) do |acc, elem|
acc << elem.chomp
end
end
end
end
end

View File

@@ -4,6 +4,8 @@ module WPScan
module Controller
# Controller to handle the API token
class VulnApi < CMSScanner::Controller::Base
ENV_KEY = 'WPSCAN_API_TOKEN'
def cli_options
[
OptString.new(['--api-token TOKEN', 'The WPVulnDB API Token to display vulnerability data'])
@@ -11,9 +13,9 @@ module WPScan
end
def before_scan
return unless ParsedCli.api_token
return unless ParsedCli.api_token || ENV.key?(ENV_KEY)
DB::VulnApi.token = ParsedCli.api_token
DB::VulnApi.token = ParsedCli.api_token || ENV[ENV_KEY]
api_status = DB::VulnApi.status

View File

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

View File

@@ -16,8 +16,7 @@ module WPScan
target.url(path),
confidence: 70,
found_by: DIRECT_ACCESS,
interesting_entries: target.directory_listing_entries(path),
references: { url: 'https://github.com/wpscanteam/wpscan/issues/422' }
interesting_entries: target.directory_listing_entries(path)
)
end
end

View File

@@ -11,11 +11,7 @@ module WPScan
return unless target.debug_log?(path)
Model::DebugLog.new(
target.url(path),
confidence: 100, found_by: DIRECT_ACCESS,
references: { url: 'https://codex.wordpress.org/Debugging_in_WordPress' }
)
Model::DebugLog.new(target.url(path), confidence: 100, found_by: DIRECT_ACCESS)
end
end
end

View File

@@ -9,14 +9,9 @@ module WPScan
def aggressive(_opts = {})
path = 'installer-log.txt'
return unless /DUPLICATOR INSTALL-LOG/.match?(target.head_and_get(path).body)
return unless /DUPLICATOR(-|\s)?(PRO|LITE)?:? INSTALL-LOG/i.match?(target.head_and_get(path).body)
Model::DuplicatorInstallerLog.new(
target.url(path),
confidence: 100,
found_by: DIRECT_ACCESS,
references: { url: 'https://www.exploit-db.com/ghdb/3981/' }
)
Model::DuplicatorInstallerLog.new(target.url(path), confidence: 100, found_by: DIRECT_ACCESS)
end
end
end

View File

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

View File

@@ -16,8 +16,7 @@ module WPScan
target.url(path),
confidence: 100,
found_by: DIRECT_ACCESS,
interesting_entries: fpd_entries,
references: { url: 'https://www.owasp.org/index.php/Full_Path_Disclosure' }
interesting_entries: fpd_entries
)
end
end

View File

@@ -7,22 +7,16 @@ module WPScan
class MuPlugins < CMSScanner::Finders::Finder
# @return [ InterestingFinding ]
def passive(_opts = {})
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, '(//@href|//@src)[contains(., "mu-plugins")]') do |uri|
next unless uri.path&.match?(pattern)
url = target.url('wp-content/mu-plugins/')
target.mu_plugins = true
return Model::MuPlugins.new(
url,
confidence: 70,
found_by: 'URLs In Homepage (Passive Detection)',
to_s: "This site has 'Must Use Plugins': #{url}",
references: { url: 'http://codex.wordpress.org/Must_Use_Plugins' }
)
return Model::MuPlugins.new(url, confidence: 70, found_by: 'URLs In Homepage (Passive Detection)')
end
nil
end
@@ -37,13 +31,7 @@ module WPScan
target.mu_plugins = true
Model::MuPlugins.new(
url,
confidence: 80,
found_by: DIRECT_ACCESS,
to_s: "This site has 'Must Use Plugins': #{url}",
references: { url: 'http://codex.wordpress.org/Must_Use_Plugins' }
)
Model::MuPlugins.new(url, confidence: 80, found_by: DIRECT_ACCESS)
end
end
end

View File

@@ -12,18 +12,12 @@ module WPScan
location = res.headers_hash['location']
return unless [200, 302].include?(res.code)
return if res.code == 302 && location =~ /wp-login\.php\?action=register/
return unless res.code == 200 || res.code == 302 && location =~ /wp-signup\.php/
return if res.code == 302 && location&.include?('wp-login.php?action=register')
return unless res.code == 200 || res.code == 302 && location&.include?('wp-signup.php')
target.multisite = true
Model::Multisite.new(
url,
confidence: 100,
found_by: DIRECT_ACCESS,
to_s: 'This site seems to be a multisite',
references: { url: 'http://codex.wordpress.org/Glossary#Multisite' }
)
Model::Multisite.new(url, confidence: 100, found_by: DIRECT_ACCESS)
end
end
end

View File

@@ -20,12 +20,7 @@ module WPScan
target.registration_enabled = true
Model::Registration.new(
res.effective_url,
confidence: 100,
found_by: DIRECT_ACCESS,
to_s: "Registration is enabled: #{res.effective_url}"
)
Model::Registration.new(res.effective_url, confidence: 100, found_by: DIRECT_ACCESS)
end
end
end

View File

@@ -13,12 +13,7 @@ module WPScan
return unless res.code == 200 && res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
Model::TmmDbMigrate.new(
url,
confidence: 100,
found_by: DIRECT_ACCESS,
references: { packetstorm: 131_957 }
)
Model::TmmDbMigrate.new(url, confidence: 100, found_by: DIRECT_ACCESS)
end
end
end

View File

@@ -13,12 +13,7 @@ module WPScan
url = target.url(path)
Model::UploadDirectoryListing.new(
url,
confidence: 100,
found_by: DIRECT_ACCESS,
to_s: "Upload directory has listing enabled: #{url}"
)
Model::UploadDirectoryListing.new(url, confidence: 100, found_by: DIRECT_ACCESS)
end
end
end

View File

@@ -14,11 +14,7 @@ module WPScan
return unless SQL_PATTERN.match?(res.body)
Model::UploadSQLDump.new(
target.url(path),
confidence: 100,
found_by: DIRECT_ACCESS
)
Model::UploadSQLDump.new(target.url(path), confidence: 100, found_by: DIRECT_ACCESS)
end
end
end

View File

@@ -11,17 +11,7 @@ module WPScan
return unless res.code == 200
Model::WPCron.new(
wp_cron_url,
confidence: 60,
found_by: DIRECT_ACCESS,
references: {
url: [
'https://www.iplocation.net/defend-wordpress-from-ddos',
'https://github.com/wpscanteam/wpscan/issues/1299'
]
}
)
Model::WPCron.new(wp_cron_url, confidence: 60, found_by: DIRECT_ACCESS)
end
def wp_cron_url

View File

@@ -1,8 +1,10 @@
# 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/urls_in_homepage'
require_relative 'main_theme/urls_in_404_page'
module WPScan
module Finders
@@ -14,9 +16,11 @@ module WPScan
# @param [ WPScan::Target ] target
def initialize(target)
finders <<
MainTheme::CssStyle.new(target) <<
MainTheme::CssStyleInHomepage.new(target) <<
MainTheme::CssStyleIn404Page.new(target) <<
MainTheme::WooFrameworkMetaGenerator.new(target) <<
MainTheme::UrlsInHomepage.new(target)
MainTheme::UrlsInHomepage.new(target) <<
MainTheme::UrlsIn404Page.new(target)
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 Finders
module MainTheme
# From the css style
class CssStyle < CMSScanner::Finders::Finder
include Finders::WpItems::URLsInHomepage
# From the CSS style in the homepage
class CssStyleInHomepage < CMSScanner::Finders::Finder
include Finders::WpItems::UrlsInPage # To have the item_code_pattern method available here
def create_theme(slug, style_url, opts)
Model::Theme.new(
@@ -20,8 +20,8 @@ module WPScan
end
def passive_from_css_href(res, opts)
target.in_scope_uris(res, '//style/@src|//link/@href') do |uri|
next unless uri.path =~ %r{/themes/([^\/]+)/style.css\z}i
target.in_scope_uris(res, '//link/@href[contains(., "style.css")]') do |uri|
next unless uri.path =~ %r{/themes/([^/]+)/style.css\z}i
return create_theme(Regexp.last_match[1], uri.to_s, opts)
end
@@ -33,7 +33,7 @@ module WPScan
code = tag.text.to_s
next if code.empty?
next unless code =~ %r{#{item_code_pattern('themes')}\\?/style\.css[^"'\( ]*}i
next unless code =~ %r{#{item_code_pattern('themes')}\\?/style\.css[^"'( ]*}i
return create_theme(Regexp.last_match[1], Regexp.last_match[0].strip, opts)
end

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
# URLs In Homepage Finder
class UrlsInHomepage < CMSScanner::Finders::Finder
include WpItems::URLsInHomepage
include WpItems::UrlsInPage
# @param [ Hash ] opts
#
@@ -13,7 +13,7 @@ module WPScan
def passive(opts = {})
found = []
slugs = items_from_links('themes', false) + items_from_codes('themes', false)
slugs = items_from_links('themes', uniq: false) + items_from_codes('themes', uniq: false)
slugs.each_with_object(Hash.new(0)) { |slug, counts| counts[slug] += 1 }.each do |slug, occurences|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 2 * occurences))
@@ -21,6 +21,11 @@ module WPScan
found
end
# @return [ Typhoeus::Response ]
def page_res
@page_res ||= target.homepage_res
end
end
end
end

View File

@@ -10,7 +10,7 @@ module WPScan
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze
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(
Regexp.last_match[1],

View File

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

View File

@@ -12,11 +12,11 @@ module WPScan
end
def valid_credentials?(response)
response.code == 200 && response.body =~ /blogName/
response.code == 200 && response.body.include?('blogName')
end
def errored_response?(response)
response.code != 200 && response.body !~ /login_error/i
response.code != 200 && response.body !~ /Incorrect username or password/i
end
end
end

View File

@@ -22,8 +22,30 @@ module WPScan
target.multi_call(methods, cache_ttl: 0).run
end
# @param [ IO ] file
# @param [ Integer ] passwords_size
# @return [ Array<String> ] The passwords from the last checked position in the file until there are
# passwords_size passwords retrieved
def passwords_from_wordlist(file, passwords_size)
pwds = []
added_pwds = 0
return pwds if passwords_size.zero?
# Make sure that the main code does not call #sysseek or #count etc
# otherwise the file descriptor will be set to somwehere else
file.each_line(chomp: true) do |line|
pwds << line
added_pwds += 1
break if added_pwds == passwords_size
end
pwds
end
# @param [ Array<Model::User> ] users
# @param [ Array<String> ] passwords
# @param [ String ] wordlist_path
# @param [ Hash ] opts
# @option opts [ Boolean ] :show_progression
# @option opts [ Integer ] :multicall_max_passwords
@@ -33,18 +55,22 @@ module WPScan
# TODO: Make rubocop happy about metrics etc
#
# rubocop:disable all
def attack(users, passwords, opts = {})
wordlist_index = 0
def attack(users, wordlist_path, opts = {})
checked_passwords = 0
wordlist = File.open(wordlist_path)
wordlist_size = wordlist.count
max_passwords = opts[:multicall_max_passwords]
current_passwords_size = passwords_size(max_passwords, users.size)
create_progress_bar(total: (passwords.size / current_passwords_size.round(1)).ceil,
create_progress_bar(total: (wordlist_size / current_passwords_size.round(1)).ceil,
show_progression: opts[:show_progression])
wordlist.sysseek(0) # reset the descriptor to the beginning of the file as it changed with #count
loop do
current_users = users.select { |user| user.password.nil? }
current_passwords = passwords[wordlist_index, current_passwords_size]
wordlist_index += current_passwords_size
current_users = users.select { |user| user.password.nil? }
current_passwords = passwords_from_wordlist(wordlist, current_passwords_size)
checked_passwords += current_passwords_size
break if current_users.empty? || current_passwords.nil? || current_passwords.empty?
@@ -76,16 +102,19 @@ module WPScan
break
end
progress_bar.total = progress_bar.progress + ((passwords.size - wordlist_index) / current_passwords_size.round(1)).ceil
begin
progress_bar.total = progress_bar.progress + ((wordlist_size - checked_passwords) / current_passwords_size.round(1)).ceil
rescue ProgressBar::InvalidProgressError
end
end
end
# Maybe a progress_bar.stop ?
end
# rubocop:disable all
# rubocop:enable all
def passwords_size(max_passwords, users_size)
return 1 if max_passwords < users_size
return 0 if users_size == 0
return 0 if users_size.zero?
max_passwords / users_size
end
@@ -94,9 +123,13 @@ module WPScan
def check_and_output_errors(res)
progress_bar.log("Incorrect response: #{res.code} / #{res.return_message}") unless res.code == 200
progress_bar.log('Parsing error, might be caused by a too high --max-passwords value (such as >= 2k)') if res.body =~ /parse error. not well formed/i
if /parse error. not well formed/i.match?(res.body)
progress_bar.log('Parsing error, might be caused by a too high --max-passwords value (such as >= 2k)')
end
progress_bar.log('The requested method is not supported') if res.body =~ /requested method [^ ]+ does not exist/i
return unless /requested method [^ ]+ does not exist/i.match?(res.body)
progress_bar.log('The requested method is not supported')
end
end
end

View File

@@ -48,7 +48,7 @@ module WPScan
#
# @return [ String, nil ] The version number detected from the stable tag
def from_stable_tag(body)
return unless body =~ /\b(?:stable tag|version):\s*(?!trunk)([0-9a-z\.-]+)/i
return unless body =~ /\b(?:stable tag|version):\s*(?!trunk)([0-9a-z.-]+)/i
number = Regexp.last_match[1]
@@ -59,7 +59,7 @@ module WPScan
#
# @return [ String, nil ] The best version number detected from the changelog section
def from_changelog_section(body)
extracted_versions = body.scan(%r{[=]+\s+(?:v(?:ersion)?\s*)?([0-9\.-]+)[ \ta-z0-9\(\)\.\-\/]*[=]+}i)
extracted_versions = body.scan(%r{=+\s+(?:v(?:ersion)?\s*)?([0-9.-]+)[ \ta-z0-9().\-/]*=+}i)
return if extracted_versions.nil? || extracted_versions.empty?
@@ -68,11 +68,9 @@ module WPScan
extracted_versions = extracted_versions.select { |x| x =~ /[0-9]+/ }
sorted = extracted_versions.sort do |x, y|
begin
Gem::Version.new(x) <=> Gem::Version.new(y)
rescue StandardError
0
end
Gem::Version.new(x) <=> Gem::Version.new(y)
rescue StandardError
0
end
sorted.last

View File

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

View File

@@ -19,8 +19,12 @@ module WPScan
def aggressive(opts = {})
found = []
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))
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, slug|
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

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

View File

@@ -30,7 +30,7 @@ module WPScan
# @return [ Version ]
def style_version
return unless Browser.get(target.style_url).body =~ /Version:[\t ]*(?!trunk)([0-9a-z\.-]+)/i
return unless Browser.get(target.style_url).body =~ /Version:[\t ]*(?!trunk)([0-9a-z.-]+)/i
Model::Version.new(
Regexp.last_match[1],

View File

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

View File

@@ -19,8 +19,12 @@ module WPScan
def aggressive(opts = {})
found = []
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))
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, slug|
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

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

View File

@@ -6,7 +6,8 @@ require_relative 'users/oembed_api'
require_relative 'users/rss_generator'
require_relative 'users/author_id_brute_forcing'
require_relative 'users/login_error_messages'
require_relative 'users/yoast_seo_author_sitemap.rb'
require_relative 'users/author_sitemap'
require_relative 'users/yoast_seo_author_sitemap'
module WPScan
module Finders
@@ -22,6 +23,7 @@ module WPScan
Users::WpJsonApi.new(target) <<
Users::OembedApi.new(target) <<
Users::RSSGenerator.new(target) <<
Users::AuthorSitemap.new(target) <<
Users::YoastSeoAuthorSitemap.new(target) <<
Users::AuthorIdBruteForcing.new(target) <<
Users::LoginErrorMessages.new(target)

View File

@@ -71,11 +71,13 @@ module WPScan
return username, 'Display Name', 50 if username
end
# @param [ String ] url
# @param [ String, Addressable::URI ] uri
#
# @return [ String, nil ]
def username_from_author_url(url)
url[%r{/author/([^/\b]+)/?}i, 1]
def username_from_author_url(uri)
uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
uri.path[%r{/author/([^/\b]+)/?}i, 1]
end
# @param [ Typhoeus::Response ] res
@@ -83,12 +85,12 @@ module WPScan
# @return [ String, nil ] The username found
def username_from_response(res)
# Permalink enabled
target.in_scope_uris(res, '//link/@href|//a/@href') do |uri|
username = username_from_author_url(uri.to_s)
target.in_scope_uris(res, '//@href[contains(., "author/")]') do |uri|
username = username_from_author_url(uri)
return username if username
end
# No permalink
# No permalink, TODO Maybe use xpath to extract the classes ?
res.body[/<body class="archive author author-([^\s]+)[ "]/i, 1]
end

View File

@@ -45,7 +45,7 @@ module WPScan
def potential_usernames(res)
usernames = []
target.in_scope_uris(res, '//a/@href') do |uri, node|
target.in_scope_uris(res, '//a/@href[contains(., "author")]') do |uri, node|
if uri.path =~ %r{/author/([^/\b]+)/?\z}i
usernames << [Regexp.last_match[1], 'Author Pattern', 100]
elsif /author=[0-9]+/.match?(uri.query)

View File

@@ -0,0 +1,36 @@
# frozen_string_literal: true
module WPScan
module Finders
module Users
# Since WP 5.5, /wp-sitemap-users-1.xml is generated and contains
# the usernames of accounts who made a post
class AuthorSitemap < CMSScanner::Finders::Finder
# @param [ Hash ] opts
#
# @return [ Array<User> ]
def aggressive(_opts = {})
found = []
Browser.get(sitemap_url).html.xpath('//url/loc').each do |user_tag|
username = user_tag.text.to_s[%r{/author/([^/]+)/}, 1]
next unless username && !username.strip.empty?
found << Model::User.new(username,
found_by: found_by,
confidence: 100,
interesting_entries: [sitemap_url])
end
found
end
# @return [ String ] The URL of the sitemap
def sitemap_url
@sitemap_url ||= target.url('wp-sitemap-users-1.xml')
end
end
end
end
end

View File

@@ -37,7 +37,7 @@ module WPScan
# usernames from the potential Users found
unames = opts[:found].map(&:username)
[*opts[:list]].each { |uname| unames << uname.chomp }
Array(opts[:list]).each { |uname| unames << uname.chomp }
unames.uniq
end

View File

@@ -13,7 +13,7 @@ module WPScan
urls.each do |url|
res = Browser.get_and_follow_location(url)
next unless res.code == 200 && res.body =~ /<dc\:creator>/i
next unless res.code == 200 && res.body =~ /<dc:creator>/i
potential_usernames = []

View File

@@ -21,7 +21,7 @@ module WPScan
loop do
current_page += 1
res = Typhoeus.get(api_url, params: { per_page: MAX_PER_PAGE, page: current_page })
res = Browser.get(api_url, params: { per_page: MAX_PER_PAGE, page: current_page })
total_pages ||= res.headers['X-WP-TotalPages'].to_i

View File

@@ -5,27 +5,7 @@ module WPScan
module Users
# The YOAST SEO plugin has an author-sitemap.xml which can leak usernames
# See https://github.com/wpscanteam/wpscan/issues/1228
class YoastSeoAuthorSitemap < CMSScanner::Finders::Finder
# @param [ Hash ] opts
#
# @return [ Array<User> ]
def aggressive(_opts = {})
found = []
Browser.get(sitemap_url).html.xpath('//url/loc').each do |user_tag|
username = user_tag.text.to_s[%r{/author/([^\/]+)/}, 1]
next unless username && !username.strip.empty?
found << Model::User.new(username,
found_by: found_by,
confidence: 100,
interesting_entries: [sitemap_url])
end
found
end
class YoastSeoAuthorSitemap < AuthorSitemap
# @return [ String ] The URL of the author-sitemap
def sitemap_url
@sitemap_url ||= target.url('author-sitemap.xml')

View File

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

View File

@@ -4,15 +4,19 @@ module WPScan
module Finders
module WpItems
# URLs In Homepage Module to use in plugins & themes finders
module URLsInHomepage
module UrlsInPage
# @param [ String ] type plugins / themes
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
#
# @return [Array<String> ] The plugins/themes detected in the href, src attributes of the homepage
def items_from_links(type, uniq = true)
# @return [ Array<String> ] The plugins/themes detected in the href, src attributes of the page
def items_from_links(type, uniq: true)
found = []
xpath = format(
'(//@href|//@src|//@data-src)[contains(., "%s")]',
type == 'plugins' ? target.plugins_dir : target.content_dir
)
target.in_scope_uris(target.homepage_res) do |uri|
target.in_scope_uris(page_res, xpath) do |uri|
next unless uri.to_s =~ item_attribute_pattern(type)
slug = Regexp.last_match[1]&.strip
@@ -27,10 +31,10 @@ module WPScan
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
#
# @return [Array<String> ] The plugins/themes detected in the javascript/style of the homepage
def items_from_codes(type, uniq = true)
def items_from_codes(type, uniq: true)
found = []
target.homepage_res.html.xpath('//script[not(@src)]|//style[not(@src)]').each do |tag|
page_res.html.xpath('//script[not(@src)]|//style[not(@src)]').each do |tag|
code = tag.text.to_s
next if code.empty?
@@ -51,7 +55,7 @@ module WPScan
#
# @return [ Regexp ]
def item_code_pattern(type)
@item_code_pattern ||= %r{["'\( ]#{item_url_pattern(type)}([^\\\/\)"']+)}i
@item_code_pattern ||= %r{["'( ]#{item_url_pattern(type)}([^\\/)"']+)}i
end
# @param [ String ] type
@@ -62,9 +66,9 @@ module WPScan
item_url = type == 'plugins' ? target.plugins_url : target.content_url
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
end
end
end

View File

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

View File

@@ -7,46 +7,130 @@ module WPScan
include References
end
#
# Empty classes for the #type to be correctly displayed (as taken from the self.class from the parent)
#
class BackupDB < InterestingFinding
def to_s
@to_s ||= "A backup directory has been found: #{url}"
end
# @return [ Hash ]
def references
@references ||= { url: ['https://github.com/wpscanteam/wpscan/issues/422'] }
end
end
class DebugLog < InterestingFinding
def to_s
@to_s ||= "Debug Log found: #{url}"
end
# @ return [ Hash ]
def references
@references ||= { url: ['https://codex.wordpress.org/Debugging_in_WordPress'] }
end
end
class DuplicatorInstallerLog < InterestingFinding
# @return [ Hash ]
def references
@references ||= { url: ['https://www.exploit-db.com/ghdb/3981/'] }
end
end
class EmergencyPwdResetScript < InterestingFinding
def references
@references ||= {
url: ['https://codex.wordpress.org/Resetting_Your_Password#Using_the_Emergency_Password_Reset_Script']
}
end
end
class FullPathDisclosure < InterestingFinding
def to_s
@to_s ||= "Full Path Disclosure found: #{url}"
end
# @return [ Hash ]
def references
@references ||= { url: ['https://www.owasp.org/index.php/Full_Path_Disclosure'] }
end
end
class MuPlugins < InterestingFinding
# @return [ String ]
def to_s
@to_s ||= "This site has 'Must Use Plugins': #{url}"
end
# @return [ Hash ]
def references
@references ||= { url: ['http://codex.wordpress.org/Must_Use_Plugins'] }
end
end
class Multisite < InterestingFinding
# @return [ String ]
def to_s
@to_s ||= 'This site seems to be a multisite'
end
# @return [ Hash ]
def references
@references ||= { url: ['http://codex.wordpress.org/Glossary#Multisite'] }
end
end
class Readme < InterestingFinding
def to_s
@to_s ||= "WordPress readme found: #{url}"
end
end
class Registration < InterestingFinding
# @return [ String ]
def to_s
@to_s ||= "Registration is enabled: #{url}"
end
end
class TmmDbMigrate < InterestingFinding
def to_s
@to_s ||= "ThemeMakers migration file found: #{url}"
end
# @return [ Hash ]
def references
@references ||= { packetstorm: [131_957] }
end
end
class UploadDirectoryListing < InterestingFinding
# @return [ String ]
def to_s
@to_s ||= "Upload directory has listing enabled: #{url}"
end
end
class UploadSQLDump < InterestingFinding
def to_s
@to_s ||= "SQL Dump found: #{url}"
end
end
class WPCron < InterestingFinding
# @return [ String ]
def to_s
@to_s ||= "The external WP-Cron seems to be enabled: #{url}"
end
# @return [ Hash ]
def references
@references ||= {
url: [
'https://www.iplocation.net/defend-wordpress-from-ddos',
'https://github.com/wpscanteam/wpscan/issues/1299'
]
}
end
end
end
end

View File

@@ -38,7 +38,7 @@ module WPScan
# @return [ Array<String> ]
def potential_readme_filenames
@potential_readme_filenames ||= [*(DB::DynamicFinders::Plugin.df_data.dig(slug, 'Readme', 'path') || super)]
@potential_readme_filenames ||= Array((DB::DynamicFinders::Plugin.df_data.dig(slug, 'Readme', 'path') || super))
end
end
end

View File

@@ -45,7 +45,7 @@ module WPScan
# @return [ Theme ]
def parent_theme
return unless template
return unless style_body =~ /^@import\surl\(["']?([^"'\)]+)["']?\);\s*$/i
return unless style_body =~ /^@import\surl\(["']?([^"')]+)["']?\);\s*$/i
opts = detection_opts.merge(
style_url: url(Regexp.last_match[1]),
@@ -101,7 +101,7 @@ module WPScan
#
# @return [ String ]
def parse_style_tag(body, tag)
value = body[/#{Regexp.escape(tag)}:[\t ]*([^\r\n\*]+)/i, 1]
value = body[/\b#{Regexp.escape(tag)}:[\t ]*([^\r\n*]+)/, 1]
value && !value.strip.empty? ? value.strip : nil
end

View File

@@ -40,9 +40,9 @@ module WPScan
def rce_132_vuln
Vulnerability.new(
'Timthumb <= 1.32 Remote Code Execution',
{ exploitdb: ['17602'] },
'RCE',
'1.33'
references: { exploitdb: ['17602'] },
type: 'RCE',
fixed_in: '1.33'
)
end
@@ -50,12 +50,12 @@ module WPScan
def rce_webshot_vuln
Vulnerability.new(
'Timthumb <= 2.8.13 WebShot Remote Code Execution',
{
references: {
url: ['http://seclists.org/fulldisclosure/2014/Jun/117', 'https://github.com/wpscanteam/wpscan/issues/519'],
cve: '2014-4663'
},
'RCE',
'2.8.14'
type: 'RCE',
fixed_in: '2.8.14'
)
end

View File

@@ -14,7 +14,7 @@ module WPScan
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 [ Target ] blog The targeted blog
@@ -23,7 +23,7 @@ module WPScan
# @option opts [ Hash ] :version_detection The options to use when looking for the version
# @option opts [ String ] :url The URL of the item
def initialize(slug, blog, opts = {})
@slug = URI.decode(slug)
@slug = Addressable::URI.unencode(slug)
@blog = blog
@uri = Addressable::URI.parse(opts[:url]) if opts[:url]
@@ -39,7 +39,7 @@ module WPScan
@vulnerabilities = []
[*db_data['vulnerabilities']].each do |json_vuln|
Array(db_data['vulnerabilities']).each do |json_vuln|
vulnerability = Vulnerability.load_from_json(json_vuln)
@vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
end
@@ -83,11 +83,6 @@ module WPScan
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
#
# @return [ String ]
@@ -95,7 +90,7 @@ module WPScan
return unless @uri
return @uri.to_s unless path
@uri.join(URI.encode(path)).to_s
@uri.join(Addressable::URI.encode(path)).to_s
end
# @return [ Boolean ]
@@ -166,7 +161,7 @@ module WPScan
# @return [ Typhoeus::Response ]
def head_and_get(path, codes = [200], params = {})
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)
end

View File

@@ -53,7 +53,7 @@ module WPScan
@vulnerabilities = []
[*db_data['vulnerabilities']].each do |json_vuln|
Array(db_data['vulnerabilities']).each do |json_vuln|
@vulnerabilities << Vulnerability.load_from_json(json_vuln)
end

View File

@@ -8,7 +8,7 @@ module WPScan
# @return [ Hash ]
def references
{
@references ||= {
url: ['http://codex.wordpress.org/XML-RPC_Pingback_API'],
metasploit: [
'auxiliary/scanner/http/wordpress_ghost_scanner',

View File

@@ -1,14 +1,14 @@
_______________________________________________________________
__ _______ _____
\ \ / / __ \ / ____|
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
\ /\ / | | ____) | (__| (_| | | | |
\/ \/ |_| |_____/ \___|\__,_|_| |_|
__ _______ _____
\ \ / / __ \ / ____|
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
\ /\ / | | ____) | (__| (_| | | | |
\/ \/ |_| |_____/ \___|\__,_|_| |_|
WordPress Security Scanner by the WPScan Team
Version <%= WPScan::VERSION %>
WordPress Security Scanner by the WPScan Team
Version <%= WPScan::VERSION %>
<%= ' ' * ((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| -%>
| - <%= entry %>
<% end -%>

View File

@@ -2,7 +2,7 @@
<% if @users.empty? -%>
<%= notice_icon %> No Valid Passwords Found.
<% else -%>
<%= notice_icon %> Valid Combinations Found:
<%= critical_icon %> Valid Combinations Found:
<% @users.each do |user| -%>
| Username: <%= user.username %>, Password: <%= user.password %>
<% end -%>

View File

@@ -9,5 +9,5 @@
<% 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.
<%= warning_icon %> You can get a free API token with 50 daily requests by registering at https://wpscan.com/register
<% end -%>

View File

@@ -1,4 +1,7 @@
| <%= critical_icon %> Title: <%= @v.title %>
<% if @v.cvss -%>
| CVSS: <%= @v.cvss[:score] %> (<%= @v.cvss[:vector] %>)
<% end -%>
<% if @v.fixed_in -%>
| Fixed in: <%= @v.fixed_in %>
<% end -%>

View File

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

View File

@@ -19,6 +19,9 @@
<% vulns.each_with_index do |v, index| -%>
{
"title": <%= v.title.to_json %>,
<% if v.cvss -%>
"cvss": <%= v.cvss.to_json %>,
<% end -%>
"fixed_in": <%= v.fixed_in.to_json %>,
"references": <%= v.references.to_json %>
}<% unless index == last_index -%>,<% end -%>

View File

@@ -8,6 +8,6 @@
"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."
"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://wpscan.com/register"
<% end -%>
},

View File

@@ -31,7 +31,7 @@ module WPScan
finder_configs(
finder_class,
Regexp.last_match[1] == 'aggressive'
aggressive: Regexp.last_match[1] == 'aggressive'
)
end

View File

@@ -16,7 +16,7 @@ module WPScan
# @param [ Symbol ] finder_class
# @param [ Boolean ] aggressive
# @return [ Hash ]
def self.finder_configs(finder_class, aggressive = false)
def self.finder_configs(finder_class, aggressive: false)
configs = {}
return configs unless allowed_classes.include?(finder_class)

View File

@@ -24,7 +24,7 @@ module WPScan
# @param [ Symbol ] finder_class
# @param [ Boolean ] aggressive
# @return [ Hash ]
def self.finder_configs(finder_class, aggressive = false)
def self.finder_configs(finder_class, aggressive: false)
configs = {}
return configs unless allowed_classes.include?(finder_class)

View File

@@ -67,13 +67,13 @@ module WPScan
# @return [ Hash ] The params for Typhoeus::Request
# @note Those params can't be overriden by CLI options
def request_params
@request_params ||= {
@request_params ||= Browser.instance.default_connect_request_params.merge(
timeout: 600,
connecttimeout: 300,
accept_encoding: 'gzip, deflate',
cache_ttl: 0,
headers: { 'User-Agent' => Browser.instance.default_user_agent, 'Referer' => nil }
}
headers: { 'User-Agent' => Browser.instance.default_user_agent }
)
end
# @return [ String ] The raw file URL associated with the given filename
@@ -85,7 +85,7 @@ module WPScan
def remote_file_checksum(filename)
url = "#{remote_file_url(filename)}.sha512"
res = Browser.get(url, request_params)
res = Typhoeus.get(url, request_params)
raise Error::Download, res if res.timed_out? || res.code != 200
res.body.chomp
@@ -126,7 +126,7 @@ module WPScan
file_path = local_file_path(filename)
file_url = remote_file_url(filename)
res = Browser.get(file_url, request_params)
res = Typhoeus.get(file_url, request_params)
raise Error::Download, res if res.timed_out? || res.code != 200
File.open(file_path, 'wb') { |f| f.write(res.body) }
@@ -139,24 +139,22 @@ module WPScan
updated = []
FILES.each do |filename|
begin
db_checksum = remote_file_checksum(filename)
db_checksum = remote_file_checksum(filename)
# Checking if the file needs to be updated
next if File.exist?(local_file_path(filename)) && db_checksum == local_file_checksum(filename)
# Checking if the file needs to be updated
next if File.exist?(local_file_path(filename)) && db_checksum == local_file_checksum(filename)
create_backup(filename)
dl_checksum = download(filename)
create_backup(filename)
dl_checksum = download(filename)
raise "#{filename}: checksums do not match" unless dl_checksum == db_checksum
raise Error::ChecksumsMismatch, filename unless dl_checksum == db_checksum
updated << filename
rescue StandardError => e
restore_backup(filename)
raise e
ensure
delete_backup(filename) if File.exist?(backup_file_path(filename))
end
updated << filename
rescue StandardError => e
restore_backup(filename)
raise e
ensure
delete_backup(filename) if File.exist?(backup_file_path(filename))
end
File.write(last_update_file, Time.now)

View File

@@ -12,7 +12,7 @@ module WPScan
# @return [ Addressable::URI ]
def self.uri
@uri ||= Addressable::URI.parse('https://wpvulndb.com/api/v3/')
@uri ||= Addressable::URI.parse('https://wpscan.com/api/v3/')
end
# @param [ String ] path
@@ -21,8 +21,10 @@ module WPScan
# @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))
# Typhoeus.get is used rather than Browser.get to avoid merging irrelevant params from the CLI
res = Typhoeus.get(uri.join(path), default_request_params.merge(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)
@@ -64,15 +66,14 @@ module WPScan
end
# @return [ Hash ]
def self.request_params
{
# @note Those params can not be overriden by CLI options
def self.default_request_params
Browser.instance.default_connect_request_params.merge(
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

View File

@@ -8,5 +8,17 @@ module WPScan
'Update required, you can not run a scan if a database file is missing.'
end
end
class ChecksumsMismatch < Standard
attr_reader :db_file
def initialize(db_file)
@db_file = db_file
end
def to_s
"#{db_file}: checksums do not match. Please try again in a few minutes."
end
end
end
end

View File

@@ -29,5 +29,11 @@ module WPScan
' use the --scope option or make sure the --url value given is the correct one'
end
end
class NoLoginInterfaceDetected < Standard
def to_s
'Could not find a login interface to perform the password attack against'
end
end
end
end

View File

@@ -17,7 +17,7 @@ module WPScan
end
# Needed to have inheritance of the @child_class_constants
# If inheritance is not needed, then the #child_class_constant can be used in the classe definition, ie
# If inheritance is not needed, then the #child_class_constant can be used in the class definition, ie
# child_class_constant :FILES, PATTERN: /aaa/i
# @return [ Hash ]
def self.child_class_constants
@@ -44,19 +44,25 @@ module WPScan
#
# @param [ Typhoeus::Response ] response
# @param [ Hash ] opts
# @return [ Mixed ]
# @return [ Mixed: nil, Object, Array ]
def find(_response, _opts = {})
raise NoMethodError
end
# @param [ Hash ] opts
# @return [ Mixed ] See #find
def passive(opts = {})
return if self.class::PATH
find(target.homepage_res, opts)
homepage_result = find(target.homepage_res, opts)
return homepage_result unless homepage_result.nil? || homepage_result.is_a?(Array) && homepage_result&.empty?
find(target.error_404_res, opts)
end
# @param [ Hash ] opts
# @return [ Mixed ] See #find
def aggressive(opts = {})
return unless self.class::PATH

View File

@@ -11,7 +11,7 @@ module WPScan
def self.child_class_constants
@child_class_constants ||= super.merge(
PARSER: nil, KEY: nil, PATTERN: /(?<v>\d+\.[\.\d]+)/, CONFIDENCE: 70
PARSER: nil, KEY: nil, PATTERN: /(?<v>\d+\.[.\d]+)/, CONFIDENCE: 70
)
end
@@ -21,13 +21,11 @@ module WPScan
parsers = ALLOWED_PARSERS.include?(self.class::PARSER) ? [self.class::PARSER] : ALLOWED_PARSERS
parsers.each do |parser|
begin
parsed = parser.respond_to?(:safe_load) ? parser.safe_load(body) : parser.load(body)
parsed = parser.respond_to?(:safe_load) ? parser.safe_load(body) : parser.load(body)
return parsed if parsed.is_a?(Hash) || parsed.is_a?(Array)
rescue StandardError
next
end
return parsed if parsed.is_a?(Hash) || parsed.is_a?(Array)
rescue StandardError
next
end
nil # Make sure nil is returned in case none of the parsers managed to parse the body correctly

View File

@@ -9,7 +9,7 @@ module WPScan
# @return [ Hash ]
def self.child_class_constants
@child_class_constants ||= super().merge(
XPATH: nil, FILES: nil, PATTERN: /(?:v|ver|version)\=(?<v>\d+\.[\.\d]+)/i, CONFIDENCE_PER_OCCURENCE: 10
XPATH: nil, FILES: nil, PATTERN: /(?:v|ver|version)=(?<v>\d+\.[.\d]+)/i, CONFIDENCE_PER_OCCURENCE: 10
)
end

View File

@@ -9,7 +9,7 @@ module WPScan
# @return [ Hash ]
def self.child_class_constants
@child_class_constants ||= super().merge(
XPATH: nil, PATTERN: /\A(?<v>\d+\.[\.\d]+)/, CONFIDENCE: 60
XPATH: nil, PATTERN: /\A(?<v>\d+\.[.\d]+)/, CONFIDENCE: 60
)
end

View File

@@ -31,9 +31,14 @@ module WPScan
passive_configs.each do |slug, configs|
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

View File

@@ -33,10 +33,12 @@ module WPScan
# @return [ Hash ]
def self.child_class_constants
@child_class_constants ||= super().merge(PATTERN: /ver\=(?<v>\d+\.[\.\d]+)/i)
@child_class_constants ||= super().merge(PATTERN: /ver=(?<v>\d+\.[.\d]+)/i)
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
def xpath
@xpath ||=

View File

@@ -13,7 +13,7 @@ end
#
# @return [ Symbol ]
def classify_slug(slug)
classified = slug.to_s.gsub(/[^a-z\d\-]/i, '-').gsub(/\-{1,}/, '_').camelize.to_s
classified = slug.to_s.gsub(/[^a-z\d\-]/i, '-').gsub(/-{1,}/, '_').camelize.to_s
classified = "D_#{classified}" if /\d/.match?(classified[0])
classified.to_sym

View File

@@ -2,7 +2,7 @@
module WPScan
# References module (which should be included along with the CMSScanner::References)
# to allow the use of the wpvulndb reference
# to allow the use of the wpvulndb reference.
module References
extend ActiveSupport::Concern
@@ -27,7 +27,7 @@ module WPScan
end
def wpvulndb_url(id)
"https://wpvulndb.com/vulnerabilities/#{id}"
"https://wpscan.com/vulnerabilities/#{id}"
end
end
end

View File

@@ -19,13 +19,13 @@ module WPScan
# @return [ Boolean ]
def vulnerable?
[@wp_version, @main_theme, @plugins, @themes, @timthumbs].each do |e|
[*e].each { |ae| return true if ae && ae.vulnerable? } # rubocop:disable Style/SafeNavigation
Array(e).each { |ae| return true if ae && ae.vulnerable? } # rubocop:disable Style/SafeNavigation
end
return true unless [*@config_backups].empty?
return true unless [*@db_exports].empty?
return true unless Array(@config_backups).empty?
return true unless Array(@db_exports).empty?
[*@users].each { |u| return true if u.password }
Array(@users).each { |u| return true if u.password }
false
end

View File

@@ -11,9 +11,9 @@ module WPScan
module WordPress
include CMSScanner::Target::Platform::PHP
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
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
# to keep the boolean state of the finding rather than re-check the whole thing again
@@ -24,34 +24,46 @@ module WPScan
# @param [ Symbol ] detection_mode
#
# @return [ Boolean ]
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
# @return [ Boolean ] Whether or not the target is running WordPress
def wordpress?(detection_mode)
in_scope_uris(homepage_res) do |uri|
return true if WORDPRESS_PATTERN.match?(uri.path) || WP_JSON_OEMBED_PATTERN.match?(uri.path)
end
return true if homepage_res.html.css('meta[name="generator"]').any? do |node|
/wordpress/i.match?(node['content'])
end
return true unless comments_from_page(/wordpress/i, homepage_res).empty?
return true if homepage_res.html.xpath('//script[not(@src)]').any? do |node|
WP_ADMIN_AJAX_PATTERN.match?(node.text)
[homepage_res, error_404_res].each do |page_res|
return true if wordpress_from_meta_comments_or_scripts?(page_res)
end
if %i[mixed aggressive].include?(detection_mode)
%w[wp-admin/install.php wp-login.php].each do |path|
return true if in_scope_uris(Browser.get_and_follow_location(url(path))).any? do |uri|
WORDPRESS_PATTERN.match?(uri.path)
res = Browser.get_and_follow_location(url(path))
next unless res.code == 200
in_scope_uris(res, '//link/@href|//script/@src') do |uri|
return true if WORDPRESS_PATTERN.match?(uri.path)
end
end
end
false
end
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
# @param [ Typhoeus::Response ] response
# @return [ Boolean ]
def wordpress_from_meta_comments_or_scripts?(response)
in_scope_uris(response, '//link/@href|//script/@src') 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 = {
'vjs' => /createCookie\('vjs','(?<c_value>\d+)',\d+\);/i
@@ -92,8 +104,9 @@ module WPScan
unless content_dir
pattern = %r{https?://s\d\.wp\.com#{WORDPRESS_PATTERN}}i.freeze
xpath = '(//@href|//@src)[contains(., "wp.com")]'
uris_from_page(homepage_res) do |uri|
uris_from_page(homepage_res, xpath) do |uri|
return true if uri.to_s.match?(pattern)
end
end
@@ -126,15 +139,16 @@ module WPScan
# the first time the method is called, and the effective_url is then used
# if suitable, otherwise the default wp-login will be.
#
# @return [ String ] The URL to the login page
# @return [ String, false ] The URL to the login page or false if not detected
def login_url
return @login_url if @login_url
return @login_url unless @login_url.nil?
@login_url = url('wp-login.php')
@login_url = url('wp-login.php') # TODO: url(ParsedCli.login_uri)
res = Browser.get_and_follow_location(@login_url)
@login_url = res.effective_url if res.effective_url =~ /wp\-login\.php\z/i && in_scope?(res.effective_url)
@login_url = res.effective_url if res.effective_url =~ /wp-login\.php\z/i && in_scope?(res.effective_url)
@login_url = false if res.code == 404
@login_url
end

View File

@@ -19,13 +19,15 @@ module WPScan
# scope_url_pattern is from CMSScanner::Target
pattern = %r{#{scope_url_pattern}([\w\s\-/]+?)\\?/(?:themes|plugins|uploads|cache)\\?/}i
in_scope_uris(homepage_res) do |uri|
return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
end
[homepage_res, error_404_res].each do |page_res|
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, homepage_res) do |match|
return @content_dir = match[1]
# 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
return @content_dir = 'wp-content' if default_content_dir_exists?
@@ -69,7 +71,7 @@ module WPScan
#
# @return [ String ]
def plugin_url(slug)
plugins_uri.join("#{URI.encode(slug)}/").to_s
plugins_uri.join("#{Addressable::URI.encode(slug)}/").to_s
end
# @return [ String ]
@@ -91,7 +93,7 @@ module WPScan
#
# @return [ String ]
def theme_url(slug)
themes_uri.join("#{URI.encode(slug)}/").to_s
themes_uri.join("#{Addressable::URI.encode(slug)}/").to_s
end
# @return [ String, False ] String of the sub_dir found, false otherwise
@@ -102,10 +104,13 @@ module WPScan
return @sub_dir unless @sub_dir.nil?
# url_pattern is from CMSScanner::Target
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp-includes/)}i
xpath = '(//@src|//@href|//@data-src)[contains(., "xmlrpc.php") or contains(., "wp-includes/")]'
in_scope_uris(homepage_res) do |uri|
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
[homepage_res, error_404_res].each do |page_res|
in_scope_uris(page_res, xpath) do |uri|
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
end
end
@sub_dir = false
@@ -119,9 +124,9 @@ module WPScan
def url(path = nil)
return @uri.to_s unless path
if %r{wp\-content/plugins}i.match?(path)
if %r{wp-content/plugins}i.match?(path)
path = +path.gsub('wp-content/plugins', plugins_dir)
elsif /wp\-content/i.match?(path)
elsif /wp-content/i.match?(path)
path = +path.gsub('wp-content', content_dir)
elsif path[0] != '/' && sub_dir
path = "#{sub_dir}/#{path}"

View File

@@ -2,5 +2,5 @@
# Version
module WPScan
VERSION = '3.7.3'
VERSION = '3.8.8'
end

View File

@@ -18,9 +18,10 @@ module WPScan
new(
json_data['title'],
references,
json_data['vuln_type'],
json_data['fixed_in']
references: references,
type: json_data['vuln_type'],
fixed_in: json_data['fixed_in'],
cvss: json_data['cvss']&.symbolize_keys
)
end
end

View File

@@ -52,9 +52,10 @@ describe WPScan::Controller::Core do
%i[apache iis nginx].each do |server|
context "when #{server}" do
let(:cli_args) { "#{super()} --server #{server}" }
let(:servers) { [:Apache, nil, :IIS, :Nginx] }
it "loads the #{server.capitalize} module and returns :#{server}" do
@stubbed_server = [:Apache, nil, :IIS, :Nginx].sample
@stubbed_server = servers.sample
@expected = server == :iis ? :IIS : server.to_s.camelize.to_sym
end
end
@@ -70,7 +71,7 @@ describe WPScan::Controller::Core do
let(:cli_args) { "#{super()} --no-update" }
it 'raises an error' do
expect { core.update_db_required? }. to raise_error(WPScan::Error::MissingDatabaseFile)
expect { core.update_db_required? }.to raise_error(WPScan::Error::MissingDatabaseFile)
end
end

View File

@@ -1,5 +1,24 @@
# frozen_string_literal: true
XMLRPC_FAILED_BODY = '
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>405</int></value>
</member>
<member>
<name>faultString</name>
<value><string>%s</string></value>
</member>
</struct>
</value>
</fault>
</methodResponse>'
describe WPScan::Controller::PasswordAttack do
subject(:controller) { described_class.new }
let(:target_url) { 'http://ex.lo/' }
@@ -21,7 +40,7 @@ describe WPScan::Controller::PasswordAttack do
describe '#users' do
context 'when no --usernames' do
it 'calles target.users' do
it 'calls target.users' do
expect(controller.target).to receive(:users)
controller.users
end
@@ -40,10 +59,6 @@ describe WPScan::Controller::PasswordAttack do
end
end
describe '#passwords' do
xit
end
describe '#run' do
context 'when no --passwords is supplied' do
it 'does not run the attacker' do
@@ -85,20 +100,34 @@ describe WPScan::Controller::PasswordAttack do
end
context 'when wp.getUsersBlogs method listed' do
before { expect(xmlrpc).to receive(:available_methods).and_return(%w[wp.getUsersBlogs m2]) }
before do
expect(xmlrpc).to receive(:available_methods).and_return(%w[wp.getUsersBlogs m2])
stub_request(:post, xmlrpc.url).to_return(body: body)
end
context 'when wp.getUsersBlogs method disabled' do
it 'returns false' do
stub_request(:post, xmlrpc.url).to_return(body: 'XML-RPC services are disabled on this site.')
context 'when blog is in EN' do
let(:body) { format(XMLRPC_FAILED_BODY, 'XML-RPC services are disabled on this site.') }
expect(controller.xmlrpc_get_users_blogs_enabled?).to be false
it 'returns false' do
expect(controller.xmlrpc_get_users_blogs_enabled?).to be false
end
end
context 'when blog is in FR' do
let(:body) { format(XMLRPC_FAILED_BODY, 'Les services XML-RPC sont désactivés sur ce site.') }
it 'returns false' do
expect(controller.xmlrpc_get_users_blogs_enabled?).to be false
end
end
end
context 'when wp.getUsersBlogs method enabled' do
it 'returns true' do
stub_request(:post, xmlrpc.url).to_return(body: 'Incorrect username or password.')
let(:body) { 'Incorrect username or password.' }
it 'returns true' do
expect(controller.xmlrpc_get_users_blogs_enabled?).to be true
end
end
@@ -107,15 +136,34 @@ describe WPScan::Controller::PasswordAttack do
end
describe '#attacker' do
before do
allow(controller.target).to receive(:sub_dir)
controller.target.instance_variable_set(:@login_url, nil)
end
context 'when --password-attack provided' do
let(:cli_args) { "#{super()} --password-attack #{attack}" }
context 'when wp-login' do
before { stub_request(:get, controller.target.url('wp-login.php')).to_return(status: status) }
let(:attack) { 'wp-login' }
it 'returns the correct object' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target
context 'when available' do
let(:status) { 200 }
it 'returns the correct object' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target
end
end
context 'when not available (404)' do
let(:status) { 404 }
it 'raises an error' do
expect { controller.attacker }.to raise_error(WPScan::Error::NoLoginInterfaceDetected)
end
end
end
@@ -172,11 +220,26 @@ describe WPScan::Controller::PasswordAttack do
context 'when automatic detection' do
context 'when xmlrpc_get_users_blogs_enabled? is false' do
it 'returns the WpLogin' do
before do
expect(controller).to receive(:xmlrpc_get_users_blogs_enabled?).and_return(false)
stub_request(:get, controller.target.url('wp-login.php')).to_return(status: status)
end
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target
context 'when wp-login available' do
let(:status) { 200 }
it 'returns the WpLogin' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target
end
end
context 'when wp-login.php not available' do
let(:status) { 404 }
it 'raises an error' do
expect { controller.attacker }.to raise_error(WPScan::Error::NoLoginInterfaceDetected)
end
end
end

View File

@@ -74,20 +74,40 @@ describe WPScan::Controller::VulnApi do
context 'when limited requests' do
let(:requests) { 100 }
it 'does not raise an error' do
it 'sets the token and does not raise an error' do
expect { controller.before_scan }.to_not raise_error
expect(WPScan::DB::VulnApi.token).to eql 'token'
end
context 'when unlimited requests' do
let(:requests) { 'Unlimited' }
it 'does not raise an error' do
it 'sets the token and does not raise an error' do
expect { controller.before_scan }.to_not raise_error
expect(WPScan::DB::VulnApi.token).to eql 'token'
end
end
end
end
end
end
context 'when token in ENV' do
before do
ENV[described_class::ENV_KEY] = 'token-from-env'
expect(WPScan::DB::VulnApi)
.to receive(:status)
.and_return('success' => true, 'plan' => 'free', 'requests_remaining' => 'Unlimited')
end
it 'sets the token and does not raise an error' do
expect { controller.before_scan }.to_not raise_error
expect(WPScan::DB::VulnApi.token).to eql 'token-from-env'
end
end
end
end

View File

@@ -9,10 +9,10 @@ describe WPScan::Finders::DbExports::KnownLocations do
describe '#potential_urls' 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
it 'replace {domain_name} by its value' do
it 'replaces {domain_name} by its value' do
expect(finder.potential_urls(opts).keys).to eql %w[
http://ex.lo/aa/ex.sql
http://ex.lo/aa/wordpress.sql
@@ -22,11 +22,53 @@ describe WPScan::Finders::DbExports::KnownLocations do
http://ex.lo/aa/backups/db_backup.sql
]
end
%w[dev poc www].each do |sub_domain|
context "when #{sub_domain} sub-domain" do
let(:url) { "https://#{sub_domain}.domain.tld" }
it 'replaces {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 'replaces {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 'replaces {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 'replaces {domain_name} by its correct value' do
expect(finder.potential_urls(opts).keys).to include "#{url}/domain-test.sql"
end
end
context 'when a non standard URL' do
let(:url) { 'http://dc-2' }
it 'replaces {domain_name} by its correct value' do
expect(finder.potential_urls(opts).keys).to include "#{url}/dc-2.sql"
end
end
end
describe '#aggressive' 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)
finder.potential_urls(opts).each_key do |url|

View File

@@ -35,15 +35,47 @@ describe WPScan::Finders::InterestingFindings::DuplicatorInstallerLog do
end
context 'when the body matches' do
let(:body) { File.read(fixtures.join(filename)) }
it 'returns the InterestingFinding' do
after do
expect(finder.aggressive).to eql WPScan::Model::DuplicatorInstallerLog.new(
log_url,
confidence: 100,
found_by: described_class::DIRECT_ACCESS
)
end
context 'when old versions of the file' do
let(:body) { File.read(fixtures.join('old.txt')) }
it 'returns the InterestingFinding' do
# handled in after loop above
end
end
context 'when newest versions of the file' do
context 'when PRO format 1' do
let(:body) { File.read(fixtures.join('pro.txt')) }
it 'returns the InterestingFinding' do
# handled in after loop above
end
end
context 'when PRO format 2' do
let(:body) { File.read(fixtures.join('pro2.txt')) }
it 'returns the InterestingFinding' do
# handled in after loop above
end
end
context 'when LITE' do
let(:body) { File.read(fixtures.join('lite.txt')) }
it 'returns the InterestingFinding' do
# handled in after loop above
end
end
end
end
end
end

View File

@@ -4,7 +4,7 @@ describe WPScan::Finders::InterestingFindings::EmergencyPwdResetScript do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:file_url) { url + 'emergency.php' }
let(:file_url) { "#{url}emergency.php" }
let(:fixtures) { FINDERS_FIXTURES.join('interesting_findings', 'emergency_pwd_reset_script') }
before do

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