Compare commits
194 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b07d53077 | ||
|
|
8ee9b2bc31 | ||
|
|
c5989477a4 | ||
|
|
96d8a4e4f8 | ||
|
|
e865e11731 | ||
|
|
f0997bfe0d | ||
|
|
8b67dad456 | ||
|
|
53fdac1038 | ||
|
|
534a7602e6 | ||
|
|
30f329fe43 | ||
|
|
4ce39951a9 | ||
|
|
0e9eb34626 | ||
|
|
0ff299c425 | ||
|
|
6366258ce9 | ||
|
|
bca69a026e | ||
|
|
adc26ea42a | ||
|
|
b16e8d84d7 | ||
|
|
5ee405d5a0 | ||
|
|
a5b9470636 | ||
|
|
16a3d54cb6 | ||
|
|
9677dcd978 | ||
|
|
17ea42f918 | ||
|
|
bd8915918d | ||
|
|
91db6773a0 | ||
|
|
f50680b61f | ||
|
|
3fb5d33333 | ||
|
|
f70bbb2660 | ||
|
|
589c1ac9bb | ||
|
|
d458fa1b89 | ||
|
|
dc2c99434f | ||
|
|
bbf36562d0 | ||
|
|
c458edf3e4 | ||
|
|
99c2aaef7a | ||
|
|
921096ca10 | ||
|
|
b0fbd6fa36 | ||
|
|
21bd67c44f | ||
|
|
4f142985a2 | ||
|
|
bfa89b44bc | ||
|
|
eba876e72b | ||
|
|
f1a7413e20 | ||
|
|
4d32749489 | ||
|
|
d911a16684 | ||
|
|
d7193bc755 | ||
|
|
aee9ffdb9c | ||
|
|
1f627d5e49 | ||
|
|
bb67626d09 | ||
|
|
4e0153e94a | ||
|
|
065142ff19 | ||
|
|
8bb6fae52f | ||
|
|
8cb7b81903 | ||
|
|
cb214ccda9 | ||
|
|
3fa7b96f27 | ||
|
|
7c8e259072 | ||
|
|
743d067042 | ||
|
|
50ea410718 | ||
|
|
e71182aed2 | ||
|
|
97f7963e0b | ||
|
|
6cea6a10bd | ||
|
|
344d41e365 | ||
|
|
597a8adfed | ||
|
|
5682e5483a | ||
|
|
18779edd7d | ||
|
|
63aeaea77a | ||
|
|
f51e48cb40 | ||
|
|
193372c79c | ||
|
|
34d0afe7e5 | ||
|
|
d33a9dd56d | ||
|
|
af2be90176 | ||
|
|
701fb21544 | ||
|
|
c8f010d9a6 | ||
|
|
c1ca7580e2 | ||
|
|
11d3c2cbf1 | ||
|
|
412f576aee | ||
|
|
ff98a7b23b | ||
|
|
507bac8542 | ||
|
|
3bd6cf4805 | ||
|
|
5712b31869 | ||
|
|
b0f9a0b18f | ||
|
|
f7665b460e | ||
|
|
100029b640 | ||
|
|
2b89bddf0f | ||
|
|
ca46bad8ec | ||
|
|
1ecd2600a3 | ||
|
|
28306b126b | ||
|
|
5c842e192b | ||
|
|
f9f307118d | ||
|
|
2266fa4f4b | ||
|
|
6df2564d1a | ||
|
|
b2a62ebd26 | ||
|
|
2fca30752a | ||
|
|
210eced369 | ||
|
|
08c574aff8 | ||
|
|
f4db2d65f1 | ||
|
|
23b02ade96 | ||
|
|
71d35b16ac | ||
|
|
200058c52a | ||
|
|
edb5fb202a | ||
|
|
d114c25cdb | ||
|
|
64e469568b | ||
|
|
c63d777372 | ||
|
|
ae343b8cb0 | ||
|
|
86eb5d2d57 | ||
|
|
b562d241db | ||
|
|
49b1829b78 | ||
|
|
1a5bf4035c | ||
|
|
f3810a1504 | ||
|
|
4831760c11 | ||
|
|
f375d8991e | ||
|
|
8145a4a3a6 | ||
|
|
12c9b49d4c | ||
|
|
c8eb81161e | ||
|
|
8ab246a66c | ||
|
|
8dfc4797fa | ||
|
|
7888fe1176 | ||
|
|
8a6f3056a3 | ||
|
|
5fbdf9e013 | ||
|
|
1da2f5e823 | ||
|
|
888779f81b | ||
|
|
352286e497 | ||
|
|
025ce37c05 | ||
|
|
d6c2c63679 | ||
|
|
49efbf25ea | ||
|
|
02cdee2776 | ||
|
|
7c9d4d5b05 | ||
|
|
609b7551f8 | ||
|
|
e8f215ae00 | ||
|
|
2e00aea16e | ||
|
|
dd274d77f5 | ||
|
|
58171a7b8c | ||
|
|
8b05179401 | ||
|
|
51d61a7e88 | ||
|
|
d653ce4e0e | ||
|
|
07b3826806 | ||
|
|
1baa3e23b2 | ||
|
|
0aa1f20d47 | ||
|
|
1cf330b389 | ||
|
|
1771c4b346 | ||
|
|
4c053b4873 | ||
|
|
743ba0541b | ||
|
|
cfab2a9cd7 | ||
|
|
32270efd65 | ||
|
|
7ea1acb7c1 | ||
|
|
bf91f60242 | ||
|
|
660885c0b1 | ||
|
|
15fd3b969f | ||
|
|
f1d15ca7f2 | ||
|
|
6f4f4a5924 | ||
|
|
9af0520701 | ||
|
|
2edeab558e | ||
|
|
87bf59f50b | ||
|
|
eeb69e63f7 | ||
|
|
f9435906e7 | ||
|
|
6c8adbe50e | ||
|
|
23bdb6c579 | ||
|
|
264411bfb9 | ||
|
|
2104237584 | ||
|
|
0ae2525737 | ||
|
|
b12973a837 | ||
|
|
fa0582ce0b | ||
|
|
231f5157bf | ||
|
|
8b18204a69 | ||
|
|
95eb6a732c | ||
|
|
047a188b34 | ||
|
|
d407815c30 | ||
|
|
1f0f87633b | ||
|
|
c15ff4e32e | ||
|
|
72bddca314 | ||
|
|
496fc4ebee | ||
|
|
f414e6eeb7 | ||
|
|
f09606cfa3 | ||
|
|
6304fe4c19 | ||
|
|
5f2b8f8a2e | ||
|
|
898e8d4546 | ||
|
|
f1657164d5 | ||
|
|
357e13be2b | ||
|
|
9685568c75 | ||
|
|
b316940790 | ||
|
|
2ced489e1e | ||
|
|
5969fe08d8 | ||
|
|
4a427f1ff6 | ||
|
|
9a3db275f3 | ||
|
|
475dd4d1ff | ||
|
|
57c99c4a34 | ||
|
|
966f5691a2 | ||
|
|
5088ece8a1 | ||
|
|
943d87fe17 | ||
|
|
b5363b2689 | ||
|
|
c15cb16ca8 | ||
|
|
18b7f088fc | ||
|
|
4f9822743c | ||
|
|
e7925de5bc | ||
|
|
27fc6a7279 | ||
|
|
ab5f46e955 | ||
|
|
d30d212cc5 |
@@ -12,5 +12,5 @@ spec/
|
|||||||
Dockerfile
|
Dockerfile
|
||||||
**/*.orig
|
**/*.orig
|
||||||
*.orig
|
*.orig
|
||||||
bin/wpscan-docker*
|
bin/wpscan-*
|
||||||
.wpscan/
|
.wpscan/
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Before submitting an issue, please make sure you fully read any potential error messages output and did some research on your own.
|
||||||
|
|
||||||
### Subject of the issue
|
### Subject of the issue
|
||||||
Describe your issue here.
|
Describe your issue here.
|
||||||
|
|
||||||
@@ -24,4 +35,4 @@ Things you have tried (where relevant):
|
|||||||
* Update Ruby to the latest version [ ]
|
* Update Ruby to the latest version [ ]
|
||||||
* Ensure you can reach the target site using cURL [ ]
|
* Ensure you can reach the target site using cURL [ ]
|
||||||
* Proxied WPScan through a HTTP proxy to view the raw traffic [ ]
|
* Proxied WPScan through a HTTP proxy to view the raw traffic [ ]
|
||||||
* Ensure you are using a supported Operating System (Linux and macOS) [ ]
|
* Ensure you are using a supported Operating System (Linux and macOS) [ ]
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
10
.github/ISSUE_TEMPLATE/other-issue.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/other-issue.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: Other Issue
|
||||||
|
about: Create a report which is not a related to a Bug or Feature
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Before submitting an issue, please make sure you fully read any potential error messages output and did some research on your own.
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,3 +21,6 @@ doc/
|
|||||||
# Old files from v2
|
# Old files from v2
|
||||||
cache/
|
cache/
|
||||||
data/
|
data/
|
||||||
|
|
||||||
|
# Profiling reports
|
||||||
|
bin/memprof*.report
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
require: rubocop-performance
|
||||||
AllCops:
|
AllCops:
|
||||||
TargetRubyVersion: 2.3
|
TargetRubyVersion: 2.4
|
||||||
Exclude:
|
Exclude:
|
||||||
- '*.gemspec'
|
- '*.gemspec'
|
||||||
- 'vendor/**/*'
|
- 'vendor/**/*'
|
||||||
@@ -9,6 +10,8 @@ LineLength:
|
|||||||
Max: 120
|
Max: 120
|
||||||
MethodLength:
|
MethodLength:
|
||||||
Max: 20
|
Max: 20
|
||||||
|
Exclude:
|
||||||
|
- 'app/controllers/enumeration/cli_options.rb'
|
||||||
Lint/UriEscapeUnescape:
|
Lint/UriEscapeUnescape:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
@@ -18,11 +21,11 @@ Metrics/BlockLength:
|
|||||||
- 'spec/**/*'
|
- 'spec/**/*'
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Max: 150
|
Max: 150
|
||||||
|
Exclude:
|
||||||
|
- 'app/controllers/enumeration/cli_options.rb'
|
||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 8
|
Max: 8
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Style/FrozenStringLiteralComment:
|
|
||||||
Enabled: false
|
|
||||||
Style/FormatStringToken:
|
Style/FormatStringToken:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.6.0
|
2.6.2
|
||||||
|
|||||||
15
.travis.yml
15
.travis.yml
@@ -2,25 +2,22 @@ language: ruby
|
|||||||
sudo: false
|
sudo: false
|
||||||
cache: bundler
|
cache: bundler
|
||||||
rvm:
|
rvm:
|
||||||
- 2.3.0
|
|
||||||
- 2.3.1
|
|
||||||
- 2.3.2
|
|
||||||
- 2.3.3
|
|
||||||
- 2.3.4
|
|
||||||
- 2.3.5
|
|
||||||
- 2.3.6
|
|
||||||
- 2.3.7
|
|
||||||
- 2.3.8
|
|
||||||
- 2.4.1
|
- 2.4.1
|
||||||
- 2.4.2
|
- 2.4.2
|
||||||
- 2.4.3
|
- 2.4.3
|
||||||
- 2.4.4
|
- 2.4.4
|
||||||
- 2.4.5
|
- 2.4.5
|
||||||
|
- 2.4.6
|
||||||
- 2.5.0
|
- 2.5.0
|
||||||
- 2.5.1
|
- 2.5.1
|
||||||
- 2.5.2
|
- 2.5.2
|
||||||
- 2.5.3
|
- 2.5.3
|
||||||
|
- 2.5.4
|
||||||
|
- 2.5.5
|
||||||
- 2.6.0
|
- 2.6.0
|
||||||
|
- 2.6.1
|
||||||
|
- 2.6.2
|
||||||
|
- 2.6.3
|
||||||
- ruby-head
|
- ruby-head
|
||||||
before_install:
|
before_install:
|
||||||
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM ruby:2.5.1-alpine AS builder
|
FROM ruby:2.6.3-alpine AS builder
|
||||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||||
|
|
||||||
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
||||||
@@ -19,19 +19,22 @@ RUN rake install --trace
|
|||||||
RUN chmod -R a+r /usr/local/bundle
|
RUN chmod -R a+r /usr/local/bundle
|
||||||
|
|
||||||
|
|
||||||
FROM ruby:2.5-alpine
|
FROM ruby:2.6.3-alpine
|
||||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||||
|
|
||||||
RUN adduser -h /wpscan -g WPScan -D wpscan
|
RUN adduser -h /wpscan -g WPScan -D wpscan
|
||||||
|
|
||||||
COPY --from=builder /usr/local/bundle /usr/local/bundle
|
COPY --from=builder /usr/local/bundle /usr/local/bundle
|
||||||
|
|
||||||
RUN chown -R wpscan:wpscan /wpscan
|
RUN chown -R wpscan:wpscan /wpscan
|
||||||
|
|
||||||
# runtime dependencies
|
# runtime dependencies
|
||||||
RUN apk add --no-cache libcurl procps sqlite-libs
|
RUN apk add --no-cache libcurl procps sqlite-libs
|
||||||
|
|
||||||
|
WORKDIR /wpscan
|
||||||
|
|
||||||
USER wpscan
|
USER wpscan
|
||||||
|
|
||||||
RUN /usr/local/bundle/bin/wpscan --update --verbose
|
RUN /usr/local/bundle/bin/wpscan --update --verbose
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]
|
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]
|
||||||
|
|||||||
4
Gemfile
4
Gemfile
@@ -1,2 +1,6 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
gemspec
|
gemspec
|
||||||
|
|
||||||
|
# gem 'cms_scanner', branch: 'xxx', git: 'https://github.com/wpscanteam/CMSScanner.git'
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -1,9 +1,23 @@
|
|||||||

|
<p align="center">
|
||||||
|
<a href="https://wpscan.org/">
|
||||||
|
<img src="https://raw.githubusercontent.com/wpscanteam/wpscan/gh-pages/images/wpscan_logo.png" alt="WPScan logo">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
[](https://badge.fury.io/rb/wpscan)
|
<h3 align="center">WPScan</h3>
|
||||||
[](https://travis-ci.org/wpscanteam/wpscan)
|
|
||||||
[](https://codeclimate.com/github/wpscanteam/wpscan)
|
<p align="center">
|
||||||
[](https://www.patreon.com/wpscan)
|
WordPress Vulnerability 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>
|
||||||
|
</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://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
# INSTALL
|
# INSTALL
|
||||||
|
|
||||||
@@ -15,6 +29,7 @@
|
|||||||
- Curl >= 7.21 - Recommended: latest
|
- Curl >= 7.21 - Recommended: latest
|
||||||
- The 7.29 has a segfault
|
- The 7.29 has a segfault
|
||||||
- RubyGems - Recommended: latest
|
- RubyGems - Recommended: latest
|
||||||
|
- Nokogiri might require packages to be installed via your package manager depending on your OS, see https://nokogiri.org/tutorials/installing_nokogiri.html
|
||||||
|
|
||||||
### From RubyGems (Recommended)
|
### From RubyGems (Recommended)
|
||||||
|
|
||||||
@@ -110,14 +125,6 @@ wpscan --url https://target.tld/ --enumerate u1-100
|
|||||||
|
|
||||||
** replace u1-100 with a range of your choice.
|
** replace u1-100 with a range of your choice.
|
||||||
|
|
||||||
# PROJECT HOME
|
|
||||||
|
|
||||||
[https://wpscan.org](https://wpscan.org)
|
|
||||||
|
|
||||||
# VULNERABILITY DATABASE
|
|
||||||
|
|
||||||
[https://wpvulndb.com](https://wpvulndb.com)
|
|
||||||
|
|
||||||
# LICENSE
|
# LICENSE
|
||||||
|
|
||||||
## WPScan Public Source License
|
## WPScan Public Source License
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'models'
|
require_relative 'models'
|
||||||
require_relative 'finders'
|
require_relative 'finders'
|
||||||
require_relative 'controllers'
|
require_relative 'controllers'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'controllers/core'
|
require_relative 'controllers/core'
|
||||||
require_relative 'controllers/custom_directories'
|
require_relative 'controllers/custom_directories'
|
||||||
require_relative 'controllers/wp_version'
|
require_relative 'controllers/wp_version'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Controller to add the aliases in the CLI
|
# Controller to add the aliases in the CLI
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Specific Core controller to include WordPress checks
|
# Specific Core controller to include WordPress checks
|
||||||
@@ -25,53 +27,56 @@ module WPScan
|
|||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def update_db_required?
|
def update_db_required?
|
||||||
if local_db.missing_files?
|
if local_db.missing_files?
|
||||||
raise MissingDatabaseFile if parsed_options[:update] == false
|
raise Error::MissingDatabaseFile if ParsedCli.update == false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
return parsed_options[:update] unless parsed_options[:update].nil?
|
return ParsedCli.update unless ParsedCli.update.nil?
|
||||||
|
|
||||||
return false unless user_interaction? && local_db.outdated?
|
return false unless user_interaction? && local_db.outdated?
|
||||||
|
|
||||||
output('@notice', msg: 'It seems like you have not updated the database for some time.')
|
output('@notice', msg: 'It seems like you have not updated the database for some time.')
|
||||||
print '[?] Do you want to update now? [Y]es [N]o, default: [N]'
|
print '[?] Do you want to update now? [Y]es [N]o, default: [N]'
|
||||||
|
|
||||||
Readline.readline =~ /^y/i ? true : false
|
/^y/i.match?(Readline.readline) ? true : false
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_db
|
def update_db
|
||||||
output('db_update_started')
|
output('db_update_started')
|
||||||
output('db_update_finished', updated: local_db.update, verbose: parsed_options[:verbose])
|
output('db_update_finished', updated: local_db.update, verbose: ParsedCli.verbose)
|
||||||
|
|
||||||
exit(0) unless parsed_options[:url]
|
exit(0) unless ParsedCli.url
|
||||||
end
|
end
|
||||||
|
|
||||||
def before_scan
|
def before_scan
|
||||||
@last_update = local_db.last_update
|
@last_update = local_db.last_update
|
||||||
|
|
||||||
maybe_output_banner_help_and_version # From CMS Scanner
|
maybe_output_banner_help_and_version # From CMSScanner
|
||||||
|
|
||||||
update_db if update_db_required?
|
update_db if update_db_required?
|
||||||
setup_cache
|
setup_cache
|
||||||
check_target_availability
|
check_target_availability
|
||||||
load_server_module
|
load_server_module
|
||||||
check_wordpress_state
|
check_wordpress_state
|
||||||
|
rescue Error::NotWordPress => e
|
||||||
|
target.maybe_add_cookies
|
||||||
|
raise e unless target.wordpress?(ParsedCli.detection_mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raises errors if the target is hosted on wordpress.com or is not running WordPress
|
# Raises errors if the target is hosted on wordpress.com or is not running WordPress
|
||||||
# Also check if the homepage_url is still the install url
|
# Also check if the homepage_url is still the install url
|
||||||
def check_wordpress_state
|
def check_wordpress_state
|
||||||
raise WordPressHostedError if target.wordpress_hosted?
|
raise Error::WordPressHosted if target.wordpress_hosted?
|
||||||
|
|
||||||
if Addressable::URI.parse(target.homepage_url).path =~ %r{/wp-admin/install.php$}i
|
if %r{/wp-admin/install.php$}i.match?(Addressable::URI.parse(target.homepage_url).path)
|
||||||
|
|
||||||
output('not_fully_configured', url: target.homepage_url)
|
output('not_fully_configured', url: target.homepage_url)
|
||||||
|
|
||||||
exit(WPScan::ExitCode::VULNERABLE)
|
exit(WPScan::ExitCode::VULNERABLE)
|
||||||
end
|
end
|
||||||
|
|
||||||
raise NotWordPressError unless target.wordpress?(parsed_options[:detection_mode]) || parsed_options[:force]
|
raise Error::NotWordPress unless target.wordpress?(ParsedCli.detection_mode) || ParsedCli.force
|
||||||
end
|
end
|
||||||
|
|
||||||
# Loads the related server module in the target
|
# Loads the related server module in the target
|
||||||
@@ -83,7 +88,7 @@ module WPScan
|
|||||||
server = target.server || :Apache # Tries to auto detect the server
|
server = target.server || :Apache # Tries to auto detect the server
|
||||||
|
|
||||||
# Force a specific server module to be loaded if supplied
|
# Force a specific server module to be loaded if supplied
|
||||||
case parsed_options[:server]
|
case ParsedCli.server
|
||||||
when :apache
|
when :apache
|
||||||
server = :Apache
|
server = :Apache
|
||||||
when :iis
|
when :iis
|
||||||
@@ -95,7 +100,7 @@ module WPScan
|
|||||||
mod = CMSScanner::Target::Server.const_get(server)
|
mod = CMSScanner::Target::Server.const_get(server)
|
||||||
|
|
||||||
target.extend mod
|
target.extend mod
|
||||||
WPScan::WpItem.include mod
|
Model::WpItem.include mod
|
||||||
|
|
||||||
server
|
server
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Controller to ensure that the wp-content and wp-plugins
|
# Controller to ensure that the wp-content and wp-plugins
|
||||||
@@ -5,18 +7,20 @@ module WPScan
|
|||||||
class CustomDirectories < CMSScanner::Controller::Base
|
class CustomDirectories < CMSScanner::Controller::Base
|
||||||
def cli_options
|
def cli_options
|
||||||
[
|
[
|
||||||
OptString.new(['--wp-content-dir DIR']),
|
OptString.new(['--wp-content-dir DIR',
|
||||||
OptString.new(['--wp-plugins-dir DIR'])
|
'The wp-content directory if custom or not detected, such as "wp-content"']),
|
||||||
|
OptString.new(['--wp-plugins-dir DIR',
|
||||||
|
'The plugins directory if custom or not detected, such as "wp-content/plugins"'])
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def before_scan
|
def before_scan
|
||||||
target.content_dir = parsed_options[:wp_content_dir] if parsed_options[:wp_content_dir]
|
target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir
|
||||||
target.plugins_dir = parsed_options[:wp_plugins_dir] if parsed_options[:wp_plugins_dir]
|
target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir
|
||||||
|
|
||||||
return if target.content_dir
|
return if target.content_dir(ParsedCli.detection_mode)
|
||||||
|
|
||||||
raise 'Unable to identify the wp-content dir, please supply it with --wp-content-dir'
|
raise Error::WpContentDirNotDetected
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'enumeration/cli_options'
|
require_relative 'enumeration/cli_options'
|
||||||
require_relative 'enumeration/enum_methods'
|
require_relative 'enumeration/enum_methods'
|
||||||
|
|
||||||
@@ -5,13 +7,8 @@ module WPScan
|
|||||||
module Controller
|
module Controller
|
||||||
# Enumeration Controller
|
# Enumeration Controller
|
||||||
class Enumeration < CMSScanner::Controller::Base
|
class Enumeration < CMSScanner::Controller::Base
|
||||||
def before_scan
|
|
||||||
DB::DynamicFinders::Plugin.create_versions_finders
|
|
||||||
DB::DynamicFinders::Theme.create_versions_finders
|
|
||||||
end
|
|
||||||
|
|
||||||
def run
|
def run
|
||||||
enum = parsed_options[:enumerate] || {}
|
enum = ParsedCli.enumerate || {}
|
||||||
|
|
||||||
enum_plugins if enum_plugins?(enum)
|
enum_plugins if enum_plugins?(enum)
|
||||||
enum_themes if enum_themes?(enum)
|
enum_themes if enum_themes?(enum)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Enumeration CLI Options
|
# Enumeration CLI Options
|
||||||
@@ -9,7 +11,6 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<OptParseValidator::OptBase> ]
|
# @return [ Array<OptParseValidator::OptBase> ]
|
||||||
# rubocop:disable Metrics/MethodLength
|
|
||||||
def cli_enum_choices
|
def cli_enum_choices
|
||||||
[
|
[
|
||||||
OptMultiChoices.new(
|
OptMultiChoices.new(
|
||||||
@@ -43,7 +44,6 @@ module WPScan
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/MethodLength
|
|
||||||
|
|
||||||
# @return [ Array<OptParseValidator::OptBase> ]
|
# @return [ Array<OptParseValidator::OptBase> ]
|
||||||
def cli_plugins_opts
|
def cli_plugins_opts
|
||||||
@@ -65,6 +65,11 @@ module WPScan
|
|||||||
'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
|
'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
|
||||||
'or --plugins-detection modes.'],
|
'or --plugins-detection modes.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
|
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
|
||||||
|
),
|
||||||
|
OptInteger.new(
|
||||||
|
['--plugins-threshold THRESHOLD',
|
||||||
|
'Raise an error when the number of detected plugins via known locations reaches the threshold. ' \
|
||||||
|
'Set to 0 to ignore the threshold.'], default: 100
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@@ -89,6 +94,11 @@ module WPScan
|
|||||||
'Use the supplied mode to check themes versions instead of the --detection-mode ' \
|
'Use the supplied mode to check themes versions instead of the --detection-mode ' \
|
||||||
'or --themes-detection modes.'],
|
'or --themes-detection modes.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
|
),
|
||||||
|
OptInteger.new(
|
||||||
|
['--themes-threshold THRESHOLD',
|
||||||
|
'Raise an error when the number of detected themes via known locations reaches the threshold. ' \
|
||||||
|
'Set to 0 to ignore the threshold.'], default: 20
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Enumeration Methods
|
# Enumeration Methods
|
||||||
@@ -5,13 +7,13 @@ module WPScan
|
|||||||
# @param [ String ] type (plugins or themes)
|
# @param [ String ] type (plugins or themes)
|
||||||
# @param [ Symbol ] detection_mode
|
# @param [ Symbol ] detection_mode
|
||||||
#
|
#
|
||||||
# @return [ String ] The related enumration message depending on the parsed_options and type supplied
|
# @return [ String ] The related enumration message depending on the ParsedCli and type supplied
|
||||||
def enum_message(type, detection_mode)
|
def enum_message(type, detection_mode)
|
||||||
return unless %w[plugins themes].include?(type)
|
return unless %w[plugins themes].include?(type)
|
||||||
|
|
||||||
details = if parsed_options[:enumerate][:"vulnerable_#{type}"]
|
details = if ParsedCli.enumerate[:"vulnerable_#{type}"]
|
||||||
'Vulnerable'
|
'Vulnerable'
|
||||||
elsif parsed_options[:enumerate][:"all_#{type}"]
|
elsif ParsedCli.enumerate[:"all_#{type}"]
|
||||||
'All'
|
'All'
|
||||||
else
|
else
|
||||||
'Most Popular'
|
'Most Popular'
|
||||||
@@ -37,15 +39,15 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def default_opts(type)
|
def default_opts(type)
|
||||||
mode = parsed_options[:"#{type}_detection"] || parsed_options[:detection_mode]
|
mode = ParsedCli.options[:"#{type}_detection"] || ParsedCli.detection_mode
|
||||||
|
|
||||||
{
|
{
|
||||||
mode: mode,
|
mode: mode,
|
||||||
exclude_content: parsed_options[:exclude_content_based],
|
exclude_content: ParsedCli.exclude_content_based,
|
||||||
show_progression: user_interaction?,
|
show_progression: user_interaction?,
|
||||||
version_detection: {
|
version_detection: {
|
||||||
mode: parsed_options[:"#{type}_version_detection"] || mode,
|
mode: ParsedCli.options[:"#{type}_version_detection"] || mode,
|
||||||
confidence_threshold: parsed_options[:"#{type}_version_all"] ? 0 : 100
|
confidence_threshold: ParsedCli.options[:"#{type}_version_all"] ? 0 : 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -59,7 +61,8 @@ module WPScan
|
|||||||
|
|
||||||
def enum_plugins
|
def enum_plugins
|
||||||
opts = default_opts('plugins').merge(
|
opts = default_opts('plugins').merge(
|
||||||
list: plugins_list_from_opts(parsed_options),
|
list: plugins_list_from_opts(ParsedCli.options),
|
||||||
|
threshold: ParsedCli.plugins_threshold,
|
||||||
sort: true
|
sort: true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -75,7 +78,7 @@ module WPScan
|
|||||||
|
|
||||||
plugins.each(&:version)
|
plugins.each(&:version)
|
||||||
|
|
||||||
plugins.select!(&:vulnerable?) if parsed_options[:enumerate][:vulnerable_plugins]
|
plugins.select!(&:vulnerable?) if ParsedCli.enumerate[:vulnerable_plugins]
|
||||||
|
|
||||||
output('plugins', plugins: plugins)
|
output('plugins', plugins: plugins)
|
||||||
end
|
end
|
||||||
@@ -105,7 +108,8 @@ module WPScan
|
|||||||
|
|
||||||
def enum_themes
|
def enum_themes
|
||||||
opts = default_opts('themes').merge(
|
opts = default_opts('themes').merge(
|
||||||
list: themes_list_from_opts(parsed_options),
|
list: themes_list_from_opts(ParsedCli.options),
|
||||||
|
threshold: ParsedCli.themes_threshold,
|
||||||
sort: true
|
sort: true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -121,7 +125,7 @@ module WPScan
|
|||||||
|
|
||||||
themes.each(&:version)
|
themes.each(&:version)
|
||||||
|
|
||||||
themes.select!(&:vulnerable?) if parsed_options[:enumerate][:vulnerable_themes]
|
themes.select!(&:vulnerable?) if ParsedCli.enumerate[:vulnerable_themes]
|
||||||
|
|
||||||
output('themes', themes: themes)
|
output('themes', themes: themes)
|
||||||
end
|
end
|
||||||
@@ -143,28 +147,28 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
def enum_timthumbs
|
def enum_timthumbs
|
||||||
opts = default_opts('timthumbs').merge(list: parsed_options[:timthumbs_list])
|
opts = default_opts('timthumbs').merge(list: ParsedCli.timthumbs_list)
|
||||||
|
|
||||||
output('@info', msg: "Enumerating Timthumbs #{enum_detection_message(opts[:mode])}") if user_interaction?
|
output('@info', msg: "Enumerating Timthumbs #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||||
output('timthumbs', timthumbs: target.timthumbs(opts))
|
output('timthumbs', timthumbs: target.timthumbs(opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_config_backups
|
def enum_config_backups
|
||||||
opts = default_opts('config_backups').merge(list: parsed_options[:config_backups_list])
|
opts = default_opts('config_backups').merge(list: ParsedCli.config_backups_list)
|
||||||
|
|
||||||
output('@info', msg: "Enumerating Config Backups #{enum_detection_message(opts[:mode])}") if user_interaction?
|
output('@info', msg: "Enumerating Config Backups #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||||
output('config_backups', config_backups: target.config_backups(opts))
|
output('config_backups', config_backups: target.config_backups(opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_db_exports
|
def enum_db_exports
|
||||||
opts = default_opts('db_exports').merge(list: parsed_options[:db_exports_list])
|
opts = default_opts('db_exports').merge(list: ParsedCli.db_exports_list)
|
||||||
|
|
||||||
output('@info', msg: "Enumerating DB Exports #{enum_detection_message(opts[:mode])}") if user_interaction?
|
output('@info', msg: "Enumerating DB Exports #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||||
output('db_exports', db_exports: target.db_exports(opts))
|
output('db_exports', db_exports: target.db_exports(opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_medias
|
def enum_medias
|
||||||
opts = default_opts('medias').merge(range: parsed_options[:enumerate][:medias])
|
opts = default_opts('medias').merge(range: ParsedCli.enumerate[:medias])
|
||||||
|
|
||||||
if user_interaction?
|
if user_interaction?
|
||||||
output('@info',
|
output('@info',
|
||||||
@@ -179,13 +183,13 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Boolean ] Wether or not to enumerate the users
|
# @return [ Boolean ] Wether or not to enumerate the users
|
||||||
def enum_users?(opts)
|
def enum_users?(opts)
|
||||||
opts[:users] || (parsed_options[:passwords] && !parsed_options[:username] && !parsed_options[:usernames])
|
opts[:users] || (ParsedCli.passwords && !ParsedCli.username && !ParsedCli.usernames)
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_users
|
def enum_users
|
||||||
opts = default_opts('users').merge(
|
opts = default_opts('users').merge(
|
||||||
range: enum_users_range,
|
range: enum_users_range,
|
||||||
list: parsed_options[:users_list]
|
list: ParsedCli.users_list
|
||||||
)
|
)
|
||||||
|
|
||||||
output('@info', msg: "Enumerating Users #{enum_detection_message(opts[:mode])}") if user_interaction?
|
output('@info', msg: "Enumerating Users #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||||
@@ -196,7 +200,7 @@ module WPScan
|
|||||||
# If the --enumerate is used, the default value is handled by the Option
|
# If the --enumerate is used, the default value is handled by the Option
|
||||||
# However, when using --passwords alone, the default has to be set by the code below
|
# However, when using --passwords alone, the default has to be set by the code below
|
||||||
def enum_users_range
|
def enum_users_range
|
||||||
parsed_options[:enumerate][:users] || cli_enum_choices[0].choices[:u].validate(nil)
|
ParsedCli.enumerate[:users] || cli_enum_choices[0].choices[:u].validate(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Main Theme Controller
|
# Main Theme Controller
|
||||||
@@ -16,9 +18,9 @@ module WPScan
|
|||||||
output(
|
output(
|
||||||
'theme',
|
'theme',
|
||||||
theme: target.main_theme(
|
theme: target.main_theme(
|
||||||
mode: parsed_options[:main_theme_detection] || parsed_options[:detection_mode]
|
mode: ParsedCli.main_theme_detection || ParsedCli.detection_mode
|
||||||
),
|
),
|
||||||
verbose: parsed_options[:verbose]
|
verbose: ParsedCli.verbose
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Password Attack Controller
|
# Password Attack Controller
|
||||||
@@ -22,7 +24,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
return unless parsed_options[:passwords]
|
return unless ParsedCli.passwords
|
||||||
|
|
||||||
if user_interaction?
|
if user_interaction?
|
||||||
output('@info',
|
output('@info',
|
||||||
@@ -31,13 +33,13 @@ module WPScan
|
|||||||
|
|
||||||
attack_opts = {
|
attack_opts = {
|
||||||
show_progression: user_interaction?,
|
show_progression: user_interaction?,
|
||||||
multicall_max_passwords: parsed_options[:multicall_max_passwords]
|
multicall_max_passwords: ParsedCli.multicall_max_passwords
|
||||||
}
|
}
|
||||||
|
|
||||||
begin
|
begin
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
attacker.attack(users, passwords(parsed_options[:passwords]), attack_opts) do |user|
|
attacker.attack(users, passwords(ParsedCli.passwords), attack_opts) do |user|
|
||||||
found << user
|
found << user
|
||||||
|
|
||||||
attacker.progress_bar.log("[SUCCESS] - #{user.username} / #{user.password}")
|
attacker.progress_bar.log("[SUCCESS] - #{user.username} / #{user.password}")
|
||||||
@@ -52,50 +54,63 @@ module WPScan
|
|||||||
@attacker ||= attacker_from_cli_options || attacker_from_automatic_detection
|
@attacker ||= attacker_from_cli_options || attacker_from_automatic_detection
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ WPScan::XMLRPC ]
|
# @return [ Model::XMLRPC ]
|
||||||
def xmlrpc
|
def xmlrpc
|
||||||
@xmlrpc ||= target.xmlrpc
|
@xmlrpc ||= target.xmlrpc
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ CMSScanner::Finders::Finder ]
|
# @return [ CMSScanner::Finders::Finder ]
|
||||||
def attacker_from_cli_options
|
def attacker_from_cli_options
|
||||||
return unless parsed_options[:password_attack]
|
return unless ParsedCli.password_attack
|
||||||
|
|
||||||
case parsed_options[:password_attack]
|
case ParsedCli.password_attack
|
||||||
when :wp_login
|
when :wp_login
|
||||||
WPScan::Finders::Passwords::WpLogin.new(target)
|
Finders::Passwords::WpLogin.new(target)
|
||||||
when :xmlrpc
|
when :xmlrpc
|
||||||
raise XMLRPCNotDetected unless xmlrpc
|
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||||
|
|
||||||
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||||
when :xmlrpc_multicall
|
when :xmlrpc_multicall
|
||||||
raise XMLRPCNotDetected unless xmlrpc
|
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||||
|
|
||||||
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def xmlrpc_get_users_blogs_enabled?
|
||||||
|
if xmlrpc&.enabled? &&
|
||||||
|
xmlrpc.available_methods.include?('wp.getUsersBlogs') &&
|
||||||
|
xmlrpc.method_call('wp.getUsersBlogs', [SecureRandom.hex[0, 6], SecureRandom.hex[0, 4]])
|
||||||
|
.run.body !~ /XML\-RPC services are disabled/
|
||||||
|
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ CMSScanner::Finders::Finder ]
|
# @return [ CMSScanner::Finders::Finder ]
|
||||||
def attacker_from_automatic_detection
|
def attacker_from_automatic_detection
|
||||||
if xmlrpc&.enabled? && xmlrpc.available_methods.include?('wp.getUsersBlogs')
|
if xmlrpc_get_users_blogs_enabled?
|
||||||
wp_version = target.wp_version
|
wp_version = target.wp_version
|
||||||
|
|
||||||
if wp_version && wp_version < '4.4'
|
if wp_version && wp_version < '4.4'
|
||||||
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||||
else
|
else
|
||||||
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
WPScan::Finders::Passwords::WpLogin.new(target)
|
Finders::Passwords::WpLogin.new(target)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<Users> ] The users to brute force
|
# @return [ Array<Users> ] The users to brute force
|
||||||
def users
|
def users
|
||||||
return target.users unless parsed_options[:usernames]
|
return target.users unless ParsedCli.usernames
|
||||||
|
|
||||||
parsed_options[:usernames].reduce([]) do |acc, elem|
|
ParsedCli.usernames.reduce([]) do |acc, elem|
|
||||||
acc << CMSScanner::User.new(elem.chomp)
|
acc << Model::User.new(elem.chomp)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Wp Version Controller
|
# Wp Version Controller
|
||||||
@@ -15,15 +17,15 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
def before_scan
|
def before_scan
|
||||||
WPScan::DB::DynamicFinders::Wordpress.create_versions_finders
|
DB::DynamicFinders::Wordpress.create_versions_finders
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
output(
|
output(
|
||||||
'version',
|
'version',
|
||||||
version: target.wp_version(
|
version: target.wp_version(
|
||||||
mode: parsed_options[:wp_version_detection] || parsed_options[:detection_mode],
|
mode: ParsedCli.wp_version_detection || ParsedCli.detection_mode,
|
||||||
confidence_threshold: parsed_options[:wp_version_all] ? 0 : 100,
|
confidence_threshold: ParsedCli.wp_version_all ? 0 : 100,
|
||||||
show_progression: user_interaction?
|
show_progression: user_interaction?
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'finders/interesting_findings'
|
require_relative 'finders/interesting_findings'
|
||||||
require_relative 'finders/wp_items'
|
require_relative 'finders/wp_items'
|
||||||
require_relative 'finders/wp_version'
|
require_relative 'finders/wp_version'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'config_backups/known_filenames'
|
require_relative 'config_backups/known_filenames'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module ConfigBackups
|
module ConfigBackups
|
||||||
@@ -13,11 +15,10 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(potential_urls(opts), opts) do |res|
|
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
||||||
# Might need to improve that
|
|
||||||
next unless res.body =~ /define/i && res.body !~ /<\s?html/i
|
next unless res.body =~ /define/i && res.body !~ /<\s?html/i
|
||||||
|
|
||||||
found << WPScan::ConfigBackup.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
found << Model::ConfigBackup.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'db_exports/known_locations'
|
require_relative 'db_exports/known_locations'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module DbExports
|
module DbExports
|
||||||
@@ -6,6 +8,8 @@ module WPScan
|
|||||||
class KnownLocations < CMSScanner::Finders::Finder
|
class KnownLocations < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
|
SQL_PATTERN = /(?:DROP|(?:UN)?LOCK|CREATE) TABLE|INSERT INTO/.freeze
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ String ] :list
|
# @option opts [ String ] :list
|
||||||
# @option opts [ Boolean ] :show_progression
|
# @option opts [ Boolean ] :show_progression
|
||||||
@@ -14,15 +18,23 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(potential_urls(opts), opts) do |res|
|
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
||||||
next unless res.code == 200 && res.body =~ /INSERT INTO/
|
if res.effective_url.end_with?('.zip')
|
||||||
|
next unless %r{\Aapplication/zip}i.match?(res.headers['Content-Type'])
|
||||||
|
else
|
||||||
|
next unless SQL_PATTERN.match?(res.body)
|
||||||
|
end
|
||||||
|
|
||||||
found << WPScan::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def full_request_params
|
||||||
|
@full_request_params ||= { headers: { 'Range' => 'bytes=0-3000' } }
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ String ] :list Mandatory
|
# @option opts [ String ] :list Mandatory
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'interesting_findings/readme'
|
require_relative 'interesting_findings/readme'
|
||||||
require_relative 'interesting_findings/wp_cron'
|
require_relative 'interesting_findings/wp_cron'
|
||||||
require_relative 'interesting_findings/multisite'
|
require_relative 'interesting_findings/multisite'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -6,13 +8,12 @@ module WPScan
|
|||||||
# @return [ InterestingFinding ]
|
# @return [ InterestingFinding ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
path = 'wp-content/backup-db/'
|
path = 'wp-content/backup-db/'
|
||||||
url = target.url(path)
|
res = target.head_and_get(path, [200, 403])
|
||||||
res = Browser.get(url)
|
|
||||||
|
|
||||||
return unless [200, 403].include?(res.code) && !target.homepage_or_404?(res)
|
return unless [200, 403].include?(res.code) && !target.homepage_or_404?(res)
|
||||||
|
|
||||||
WPScan::BackupDB.new(
|
Model::BackupDB.new(
|
||||||
url,
|
target.url(path),
|
||||||
confidence: 70,
|
confidence: 70,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
interesting_entries: target.directory_listing_entries(path),
|
interesting_entries: target.directory_listing_entries(path),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -9,7 +11,7 @@ module WPScan
|
|||||||
|
|
||||||
return unless target.debug_log?(path)
|
return unless target.debug_log?(path)
|
||||||
|
|
||||||
WPScan::DebugLog.new(
|
Model::DebugLog.new(
|
||||||
target.url(path),
|
target.url(path),
|
||||||
confidence: 100, found_by: DIRECT_ACCESS,
|
confidence: 100, found_by: DIRECT_ACCESS,
|
||||||
references: { url: 'https://codex.wordpress.org/Debugging_in_WordPress' }
|
references: { url: 'https://codex.wordpress.org/Debugging_in_WordPress' }
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -5,13 +7,12 @@ module WPScan
|
|||||||
class DuplicatorInstallerLog < CMSScanner::Finders::Finder
|
class DuplicatorInstallerLog < CMSScanner::Finders::Finder
|
||||||
# @return [ InterestingFinding ]
|
# @return [ InterestingFinding ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
url = target.url('installer-log.txt')
|
path = 'installer-log.txt'
|
||||||
res = Browser.get(url)
|
|
||||||
|
|
||||||
return unless res.body =~ /DUPLICATOR INSTALL-LOG/
|
return unless /DUPLICATOR INSTALL-LOG/.match?(target.head_and_get(path).body)
|
||||||
|
|
||||||
WPScan::DuplicatorInstallerLog.new(
|
Model::DuplicatorInstallerLog.new(
|
||||||
url,
|
target.url(path),
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
references: { url: 'https://www.exploit-db.com/ghdb/3981/' }
|
references: { url: 'https://www.exploit-db.com/ghdb/3981/' }
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -5,14 +7,14 @@ module WPScan
|
|||||||
class EmergencyPwdResetScript < CMSScanner::Finders::Finder
|
class EmergencyPwdResetScript < CMSScanner::Finders::Finder
|
||||||
# @return [ InterestingFinding ]
|
# @return [ InterestingFinding ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
url = target.url('/emergency.php')
|
path = 'emergency.php'
|
||||||
res = Browser.get(url)
|
res = target.head_and_get(path)
|
||||||
|
|
||||||
return unless res.code == 200 && !target.homepage_or_404?(res)
|
return unless res.code == 200 && !target.homepage_or_404?(res)
|
||||||
|
|
||||||
WPScan::EmergencyPwdResetScript.new(
|
Model::EmergencyPwdResetScript.new(
|
||||||
url,
|
target.url(path),
|
||||||
confidence: res.body =~ /password/i ? 100 : 40,
|
confidence: /password/i.match?(res.body) ? 100 : 40,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
references: {
|
references: {
|
||||||
url: 'https://codex.wordpress.org/Resetting_Your_Password#Using_the_Emergency_Password_Reset_Script'
|
url: 'https://codex.wordpress.org/Resetting_Your_Password#Using_the_Emergency_Password_Reset_Script'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -10,7 +12,7 @@ module WPScan
|
|||||||
|
|
||||||
return if fpd_entries.empty?
|
return if fpd_entries.empty?
|
||||||
|
|
||||||
WPScan::FullPathDisclosure.new(
|
Model::FullPathDisclosure.new(
|
||||||
target.url(path),
|
target.url(path),
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -7,12 +9,14 @@ module WPScan
|
|||||||
def passive(_opts = {})
|
def passive(_opts = {})
|
||||||
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
||||||
|
|
||||||
target.in_scope_urls(target.homepage_res) do |url|
|
target.in_scope_uris(target.homepage_res) do |uri|
|
||||||
next unless Addressable::URI.parse(url).path =~ pattern
|
next unless uri.path&.match?(pattern)
|
||||||
|
|
||||||
url = target.url('wp-content/mu-plugins/')
|
url = target.url('wp-content/mu-plugins/')
|
||||||
|
|
||||||
return WPScan::MuPlugins.new(
|
target.mu_plugins = true
|
||||||
|
|
||||||
|
return Model::MuPlugins.new(
|
||||||
url,
|
url,
|
||||||
confidence: 70,
|
confidence: 70,
|
||||||
found_by: 'URLs In Homepage (Passive Detection)',
|
found_by: 'URLs In Homepage (Passive Detection)',
|
||||||
@@ -31,11 +35,9 @@ module WPScan
|
|||||||
return unless [200, 401, 403].include?(res.code)
|
return unless [200, 401, 403].include?(res.code)
|
||||||
return if target.homepage_or_404?(res)
|
return if target.homepage_or_404?(res)
|
||||||
|
|
||||||
# TODO: add the check for --exclude-content once implemented ?
|
|
||||||
|
|
||||||
target.mu_plugins = true
|
target.mu_plugins = true
|
||||||
|
|
||||||
WPScan::MuPlugins.new(
|
Model::MuPlugins.new(
|
||||||
url,
|
url,
|
||||||
confidence: 80,
|
confidence: 80,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -15,7 +17,7 @@ module WPScan
|
|||||||
|
|
||||||
target.multisite = true
|
target.multisite = true
|
||||||
|
|
||||||
WPScan::Multisite.new(
|
Model::Multisite.new(
|
||||||
url,
|
url,
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -5,14 +7,14 @@ module WPScan
|
|||||||
class Readme < CMSScanner::Finders::Finder
|
class Readme < CMSScanner::Finders::Finder
|
||||||
# @return [ InterestingFinding ]
|
# @return [ InterestingFinding ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
potential_files.each do |file|
|
potential_files.each do |path|
|
||||||
url = target.url(file)
|
res = target.head_and_get(path)
|
||||||
res = Browser.get(url)
|
|
||||||
|
|
||||||
if res.code == 200 && res.body =~ /wordpress/i
|
next unless res.code == 200 && res.body =~ /wordpress/i
|
||||||
return WPScan::Readme.new(url, confidence: 100, found_by: DIRECT_ACCESS)
|
|
||||||
end
|
return Model::Readme.new(target.url(path), confidence: 100, found_by: DIRECT_ACCESS)
|
||||||
end
|
end
|
||||||
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -18,7 +20,7 @@ module WPScan
|
|||||||
|
|
||||||
target.registration_enabled = true
|
target.registration_enabled = true
|
||||||
|
|
||||||
WPScan::Registration.new(
|
Model::Registration.new(
|
||||||
res.effective_url,
|
res.effective_url,
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -7,11 +9,11 @@ module WPScan
|
|||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
path = 'wp-content/uploads/tmm_db_migrate/tmm_db_migrate.zip'
|
path = 'wp-content/uploads/tmm_db_migrate/tmm_db_migrate.zip'
|
||||||
url = target.url(path)
|
url = target.url(path)
|
||||||
res = Browser.get(url)
|
res = browser.forge_request(url, target.head_or_get_request_params).run
|
||||||
|
|
||||||
return unless res.code == 200 && res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
return unless res.code == 200 && res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
||||||
|
|
||||||
WPScan::TmmDbMigrate.new(
|
Model::TmmDbMigrate.new(
|
||||||
url,
|
url,
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -11,7 +13,7 @@ module WPScan
|
|||||||
|
|
||||||
url = target.url(path)
|
url = target.url(path)
|
||||||
|
|
||||||
WPScan::UploadDirectoryListing.new(
|
Model::UploadDirectoryListing.new(
|
||||||
url,
|
url,
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
# UploadSQLDump finder
|
# UploadSQLDump finder
|
||||||
class UploadSQLDump < CMSScanner::Finders::Finder
|
class UploadSQLDump < CMSScanner::Finders::Finder
|
||||||
SQL_PATTERN = /(?:(?:(?:DROP|CREATE) TABLE)|INSERT INTO)/.freeze
|
SQL_PATTERN = /(?:DROP|CREATE|(?:UN)?LOCK) TABLE|INSERT INTO/.freeze
|
||||||
|
|
||||||
# @return [ InterestingFinding ]
|
# @return [ InterestingFinding ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
url = dump_url
|
path = 'wp-content/uploads/dump.sql'
|
||||||
res = Browser.get(url)
|
res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } })
|
||||||
|
|
||||||
return unless res.code == 200 && res.body =~ SQL_PATTERN
|
return unless SQL_PATTERN.match?(res.body)
|
||||||
|
|
||||||
WPScan::UploadSQLDump.new(
|
Model::UploadSQLDump.new(
|
||||||
url,
|
target.url(path),
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS
|
found_by: DIRECT_ACCESS
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def dump_url
|
|
||||||
target.url('wp-content/uploads/dump.sql')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -9,7 +11,7 @@ module WPScan
|
|||||||
|
|
||||||
return unless res.code == 200
|
return unless res.code == 200
|
||||||
|
|
||||||
WPScan::WPCron.new(
|
Model::WPCron.new(
|
||||||
wp_cron_url,
|
wp_cron_url,
|
||||||
confidence: 60,
|
confidence: 60,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'main_theme/css_style'
|
require_relative 'main_theme/css_style'
|
||||||
require_relative 'main_theme/woo_framework_meta_generator'
|
require_relative 'main_theme/woo_framework_meta_generator'
|
||||||
require_relative 'main_theme/urls_in_homepage'
|
require_relative 'main_theme/urls_in_homepage'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module MainTheme
|
module MainTheme
|
||||||
@@ -6,7 +8,7 @@ module WPScan
|
|||||||
include Finders::WpItems::URLsInHomepage
|
include Finders::WpItems::URLsInHomepage
|
||||||
|
|
||||||
def create_theme(slug, style_url, opts)
|
def create_theme(slug, style_url, opts)
|
||||||
WPScan::Theme.new(
|
Model::Theme.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by, confidence: 70, style_url: style_url)
|
opts.merge(found_by: found_by, confidence: 70, style_url: style_url)
|
||||||
@@ -18,10 +20,10 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
def passive_from_css_href(res, opts)
|
def passive_from_css_href(res, opts)
|
||||||
target.in_scope_urls(res, '//style/@src|//link/@href') do |url|
|
target.in_scope_uris(res, '//style/@src|//link/@href') do |uri|
|
||||||
next unless Addressable::URI.parse(url).path =~ %r{/themes/([^\/]+)/style.css\z}i
|
next unless uri.path =~ %r{/themes/([^\/]+)/style.css\z}i
|
||||||
|
|
||||||
return create_theme(Regexp.last_match[1], url, opts)
|
return create_theme(Regexp.last_match[1], uri.to_s, opts)
|
||||||
end
|
end
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module MainTheme
|
module MainTheme
|
||||||
@@ -14,7 +16,7 @@ module WPScan
|
|||||||
slugs = items_from_links('themes', false) + items_from_codes('themes', false)
|
slugs = items_from_links('themes', false) + items_from_codes('themes', false)
|
||||||
|
|
||||||
slugs.each_with_object(Hash.new(0)) { |slug, counts| counts[slug] += 1 }.each do |slug, occurences|
|
slugs.each_with_object(Hash.new(0)) { |slug, counts| counts[slug] += 1 }.each do |slug, occurences|
|
||||||
found << WPScan::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 2 * occurences))
|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 2 * occurences))
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module MainTheme
|
module MainTheme
|
||||||
@@ -10,7 +12,7 @@ module WPScan
|
|||||||
def passive(opts = {})
|
def passive(opts = {})
|
||||||
return unless target.homepage_res.body =~ PATTERN
|
return unless target.homepage_res.body =~ PATTERN
|
||||||
|
|
||||||
WPScan::Theme.new(
|
Model::Theme.new(
|
||||||
Regexp.last_match[1],
|
Regexp.last_match[1],
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by, confidence: 80)
|
opts.merge(found_by: found_by, confidence: 80)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'medias/attachment_brute_forcing'
|
require_relative 'medias/attachment_brute_forcing'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Medias
|
module Medias
|
||||||
@@ -15,7 +17,7 @@ module WPScan
|
|||||||
enumerate(target_urls(opts), opts) do |res|
|
enumerate(target_urls(opts), opts) do |res|
|
||||||
next unless res.code == 200
|
next unless res.code == 200
|
||||||
|
|
||||||
found << WPScan::Media.new(res.effective_url, opts.merge(found_by: found_by, confidence: 100))
|
found << Model::Media.new(res.effective_url, opts.merge(found_by: found_by, confidence: 100))
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'passwords/wp_login'
|
require_relative 'passwords/wp_login'
|
||||||
require_relative 'passwords/xml_rpc'
|
require_relative 'passwords/xml_rpc'
|
||||||
require_relative 'passwords/xml_rpc_multicall'
|
require_relative 'passwords/xml_rpc_multicall'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Passwords
|
module Passwords
|
||||||
@@ -10,7 +12,8 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
def valid_credentials?(response)
|
def valid_credentials?(response)
|
||||||
response.code == 302
|
response.code == 302 &&
|
||||||
|
[*response.headers['Set-Cookie']]&.any? { |cookie| cookie =~ /wordpress_logged_in_/i }
|
||||||
end
|
end
|
||||||
|
|
||||||
def errored_response?(response)
|
def errored_response?(response)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Passwords
|
module Passwords
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Passwords
|
module Passwords
|
||||||
@@ -20,13 +22,13 @@ module WPScan
|
|||||||
target.multi_call(methods).run
|
target.multi_call(methods).run
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Array<CMSScanner::User> ] users
|
# @param [ Array<Model::User> ] users
|
||||||
# @param [ Array<String> ] passwords
|
# @param [ Array<String> ] passwords
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ Boolean ] :show_progression
|
# @option opts [ Boolean ] :show_progression
|
||||||
# @option opts [ Integer ] :multicall_max_passwords
|
# @option opts [ Integer ] :multicall_max_passwords
|
||||||
#
|
#
|
||||||
# @yield [ CMSScanner::User ] When a valid combination is found
|
# @yield [ Model::User ] When a valid combination is found
|
||||||
#
|
#
|
||||||
# TODO: Make rubocop happy about metrics etc
|
# TODO: Make rubocop happy about metrics etc
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'plugin_version/readme'
|
require_relative 'plugin_version/readme'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
@@ -7,29 +9,19 @@ module WPScan
|
|||||||
class Base
|
class Base
|
||||||
include CMSScanner::Finders::UniqueFinder
|
include CMSScanner::Finders::UniqueFinder
|
||||||
|
|
||||||
# @param [ WPScan::Plugin ] plugin
|
# @param [ Model::Plugin ] plugin
|
||||||
def initialize(plugin)
|
def initialize(plugin)
|
||||||
finders << PluginVersion::Readme.new(plugin)
|
finders << PluginVersion::Readme.new(plugin)
|
||||||
|
|
||||||
load_specific_finders(plugin)
|
create_and_load_dynamic_versions_finders(plugin)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Load the finders associated with the plugin
|
# Create the dynamic version finders related to the plugin and register them
|
||||||
#
|
#
|
||||||
# @param [ WPScan::Plugin ] plugin
|
# @param [ Model::Plugin ] plugin
|
||||||
def load_specific_finders(plugin)
|
def create_and_load_dynamic_versions_finders(plugin)
|
||||||
module_name = plugin.classify
|
DB::DynamicFinders::Plugin.create_versions_finders(plugin.slug).each do |finder|
|
||||||
|
finders << finder.new(plugin)
|
||||||
return unless Finders::PluginVersion.constants.include?(module_name)
|
|
||||||
|
|
||||||
mod = Finders::PluginVersion.const_get(module_name)
|
|
||||||
|
|
||||||
mod.constants.each do |constant|
|
|
||||||
c = mod.const_get(constant)
|
|
||||||
|
|
||||||
next unless c.is_a?(Class)
|
|
||||||
|
|
||||||
finders << c.new(plugin)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module PluginVersion
|
module PluginVersion
|
||||||
@@ -7,21 +9,23 @@ module WPScan
|
|||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
found_by_msg = 'Readme - %s (Aggressive Detection)'
|
found_by_msg = 'Readme - %s (Aggressive Detection)'
|
||||||
|
|
||||||
WPScan::WpItem::READMES.each do |file|
|
# The target(plugin)#readme_url can't be used directly here
|
||||||
url = target.url(file)
|
# as if the --detection-mode is passive, it will always return nil
|
||||||
res = Browser.get(url)
|
target.potential_readme_filenames.each do |file|
|
||||||
|
res = target.head_and_get(file)
|
||||||
|
|
||||||
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
|
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
|
||||||
|
|
||||||
return numbers.reduce([]) do |a, e|
|
return numbers.reduce([]) do |a, e|
|
||||||
a << WPScan::Version.new(
|
a << Model::Version.new(
|
||||||
e[0],
|
e[0],
|
||||||
found_by: format(found_by_msg, e[1]),
|
found_by: format(found_by_msg, e[1]),
|
||||||
confidence: e[2],
|
confidence: e[2],
|
||||||
interesting_entries: [url]
|
interesting_entries: [res.effective_url]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -48,7 +52,7 @@ module WPScan
|
|||||||
|
|
||||||
number = Regexp.last_match[1]
|
number = Regexp.last_match[1]
|
||||||
|
|
||||||
number if number =~ /[0-9]+/
|
number if /[0-9]+/.match?(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ String ] body
|
# @param [ String ] body
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'plugins/urls_in_homepage'
|
require_relative 'plugins/urls_in_homepage'
|
||||||
require_relative 'plugins/known_locations'
|
require_relative 'plugins/known_locations'
|
||||||
# From the DynamicFinders
|
# From the DynamicFinders
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'BodyPattern'
|
# Plugins finder from Dynamic Finder 'BodyPattern'
|
||||||
class BodyPattern < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class BodyPattern < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 30
|
DEFAULT_CONFIDENCE = 30
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
@@ -13,9 +15,9 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||||
def process_response(opts, response, slug, klass, config)
|
def process_response(opts, response, slug, klass, config)
|
||||||
return unless response.body =~ config['pattern']
|
return unless response.body&.match?(config['pattern'])
|
||||||
|
|
||||||
Plugin.new(
|
Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from the Dynamic Finder 'Comment'
|
# Plugins finder from the Dynamic Finder 'Comment'
|
||||||
class Comment < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class Comment < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 30
|
DEFAULT_CONFIDENCE = 30
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
@@ -16,9 +18,9 @@ module WPScan
|
|||||||
response.html.xpath(config['xpath'] || '//comment()').each do |node|
|
response.html.xpath(config['xpath'] || '//comment()').each do |node|
|
||||||
comment = node.text.to_s.strip
|
comment = node.text.to_s.strip
|
||||||
|
|
||||||
next unless comment =~ config['pattern']
|
next unless comment&.match?(config['pattern'])
|
||||||
|
|
||||||
return Plugin.new(
|
return Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'ConfigParser'
|
# Plugins finder from Dynamic Finder 'ConfigParser'
|
||||||
class ConfigParser < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class ConfigParser < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 40
|
DEFAULT_CONFIDENCE = 40
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
@@ -19,7 +21,7 @@ module WPScan
|
|||||||
# when checking for plugins
|
# when checking for plugins
|
||||||
#
|
#
|
||||||
|
|
||||||
Plugin.new(
|
Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'HeaderPattern'
|
# Plugins finder from Dynamic Finder 'HeaderPattern'
|
||||||
class HeaderPattern < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class HeaderPattern < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 30
|
DEFAULT_CONFIDENCE = 30
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
@@ -18,7 +20,7 @@ module WPScan
|
|||||||
configs.each do |klass, config|
|
configs.each do |klass, config|
|
||||||
next unless headers[config['header']] && headers[config['header']].to_s =~ config['pattern']
|
next unless headers[config['header']] && headers[config['header']].to_s =~ config['pattern']
|
||||||
|
|
||||||
found << Plugin.new(
|
found << Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from the Dynamic Finder 'JavascriptVar'
|
# Plugins finder from the Dynamic Finder 'JavascriptVar'
|
||||||
class JavascriptVar < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class JavascriptVar < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 60
|
DEFAULT_CONFIDENCE = 60
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
@@ -16,7 +18,7 @@ module WPScan
|
|||||||
response.html.xpath(config['xpath'] || '//script[not(@src)]').each do |node|
|
response.html.xpath(config['xpath'] || '//script[not(@src)]').each do |node|
|
||||||
next if config['pattern'] && !node.text.match(config['pattern'])
|
next if config['pattern'] && !node.text.match(config['pattern'])
|
||||||
|
|
||||||
return Plugin.new(
|
return Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
@@ -5,6 +7,11 @@ module WPScan
|
|||||||
class KnownLocations < CMSScanner::Finders::Finder
|
class KnownLocations < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
|
# @return [ Array<Integer> ]
|
||||||
|
def valid_response_codes
|
||||||
|
@valid_response_codes ||= [200, 401, 403, 500].freeze
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ String ] :list
|
# @option opts [ String ] :list
|
||||||
#
|
#
|
||||||
@@ -12,12 +19,10 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts) do |res, slug|
|
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||||
# TODO: follow the location (from enumerate()) and remove the 301 here ?
|
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
# As a result, it might remove false positive due to redirection to the homepage
|
|
||||||
next unless [200, 401, 403, 301].include?(res.code)
|
|
||||||
|
|
||||||
found << WPScan::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
raise Error::PluginsThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
@@ -30,10 +35,9 @@ module WPScan
|
|||||||
def target_urls(opts = {})
|
def target_urls(opts = {})
|
||||||
slugs = opts[:list] || DB::Plugins.vulnerable_slugs
|
slugs = opts[:list] || DB::Plugins.vulnerable_slugs
|
||||||
urls = {}
|
urls = {}
|
||||||
plugins_url = target.plugins_url
|
|
||||||
|
|
||||||
slugs.each do |slug|
|
slugs.each do |slug|
|
||||||
urls["#{plugins_url}#{URI.encode(slug)}/"] = slug
|
urls[target.plugin_url(slug)] = slug
|
||||||
end
|
end
|
||||||
|
|
||||||
urls
|
urls
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'QueryParameter'
|
# Plugins finder from Dynamic Finder 'QueryParameter'
|
||||||
class QueryParameter < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class QueryParameter < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 10
|
DEFAULT_CONFIDENCE = 10
|
||||||
|
|
||||||
def passive(_opts = {})
|
def passive(_opts = {})
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
@@ -14,7 +16,7 @@ module WPScan
|
|||||||
found = []
|
found = []
|
||||||
|
|
||||||
(items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.each do |slug|
|
(items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.each do |slug|
|
||||||
found << Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from the Dynamic Finder 'Xpath'
|
# Plugins finder from the Dynamic Finder 'Xpath'
|
||||||
class Xpath < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class Xpath < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 40
|
DEFAULT_CONFIDENCE = 40
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
@@ -16,7 +18,7 @@ module WPScan
|
|||||||
response.html.xpath(config['xpath']).each do |node|
|
response.html.xpath(config['xpath']).each do |node|
|
||||||
next if config['pattern'] && !node.text.match(config['pattern'])
|
next if config['pattern'] && !node.text.match(config['pattern'])
|
||||||
|
|
||||||
return Plugin.new(
|
return Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'theme_version/style'
|
require_relative 'theme_version/style'
|
||||||
require_relative 'theme_version/woo_framework_meta_generator'
|
require_relative 'theme_version/woo_framework_meta_generator'
|
||||||
|
|
||||||
@@ -8,31 +10,21 @@ module WPScan
|
|||||||
class Base
|
class Base
|
||||||
include CMSScanner::Finders::UniqueFinder
|
include CMSScanner::Finders::UniqueFinder
|
||||||
|
|
||||||
# @param [ WPScan::Theme ] theme
|
# @param [ Model::Theme ] theme
|
||||||
def initialize(theme)
|
def initialize(theme)
|
||||||
finders <<
|
finders <<
|
||||||
ThemeVersion::Style.new(theme) <<
|
ThemeVersion::Style.new(theme) <<
|
||||||
ThemeVersion::WooFrameworkMetaGenerator.new(theme)
|
ThemeVersion::WooFrameworkMetaGenerator.new(theme)
|
||||||
|
|
||||||
load_specific_finders(theme)
|
create_and_load_dynamic_versions_finders(theme)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Load the finders associated with the theme
|
# Create the dynamic version finders related to the theme and register them
|
||||||
#
|
#
|
||||||
# @param [ WPScan::Theme ] theme
|
# @param [ Model::Theme ] theme
|
||||||
def load_specific_finders(theme)
|
def create_and_load_dynamic_versions_finders(theme)
|
||||||
module_name = theme.classify
|
DB::DynamicFinders::Theme.create_versions_finders(theme.slug).each do |finder|
|
||||||
|
finders << finder.new(theme)
|
||||||
return unless Finders::ThemeVersion.constants.include?(module_name)
|
|
||||||
|
|
||||||
mod = Finders::ThemeVersion.const_get(module_name)
|
|
||||||
|
|
||||||
mod.constants.each do |constant|
|
|
||||||
c = mod.const_get(constant)
|
|
||||||
|
|
||||||
next unless c.is_a?(Class)
|
|
||||||
|
|
||||||
finders << c.new(theme)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module ThemeVersion
|
module ThemeVersion
|
||||||
@@ -30,7 +32,7 @@ module WPScan
|
|||||||
def style_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
|
||||||
|
|
||||||
WPScan::Version.new(
|
Model::Version.new(
|
||||||
Regexp.last_match[1],
|
Regexp.last_match[1],
|
||||||
found_by: found_by,
|
found_by: found_by,
|
||||||
confidence: 80,
|
confidence: 80,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module ThemeVersion
|
module ThemeVersion
|
||||||
@@ -11,7 +13,7 @@ module WPScan
|
|||||||
|
|
||||||
return unless Regexp.last_match[1] == target.slug
|
return unless Regexp.last_match[1] == target.slug
|
||||||
|
|
||||||
WPScan::Version.new(Regexp.last_match[2], found_by: found_by, confidence: 80)
|
Model::Version.new(Regexp.last_match[2], found_by: found_by, confidence: 80)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'themes/urls_in_homepage'
|
require_relative 'themes/urls_in_homepage'
|
||||||
require_relative 'themes/known_locations'
|
require_relative 'themes/known_locations'
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Themes
|
module Themes
|
||||||
@@ -5,6 +7,11 @@ module WPScan
|
|||||||
class KnownLocations < CMSScanner::Finders::Finder
|
class KnownLocations < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
|
# @return [ Array<Integer> ]
|
||||||
|
def valid_response_codes
|
||||||
|
@valid_response_codes ||= [200, 401, 403, 500].freeze
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ String ] :list
|
# @option opts [ String ] :list
|
||||||
#
|
#
|
||||||
@@ -12,12 +19,10 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts) do |res, slug|
|
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||||
# TODO: follow the location (from enumerate()) and remove the 301 here ?
|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
# As a result, it might remove false positive due to redirection to the homepage
|
|
||||||
next unless [200, 401, 403, 301].include?(res.code)
|
|
||||||
|
|
||||||
found << WPScan::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
raise Error::ThemesThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
@@ -28,12 +33,11 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def target_urls(opts = {})
|
def target_urls(opts = {})
|
||||||
slugs = opts[:list] || DB::Themes.vulnerable_slugs
|
slugs = opts[:list] || DB::Themes.vulnerable_slugs
|
||||||
urls = {}
|
urls = {}
|
||||||
themes_url = target.url('wp-content/themes/')
|
|
||||||
|
|
||||||
slugs.each do |slug|
|
slugs.each do |slug|
|
||||||
urls["#{themes_url}#{URI.encode(slug)}/"] = slug
|
urls[target.theme_url(slug)] = slug
|
||||||
end
|
end
|
||||||
|
|
||||||
urls
|
urls
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Themes
|
module Themes
|
||||||
@@ -12,7 +14,7 @@ module WPScan
|
|||||||
found = []
|
found = []
|
||||||
|
|
||||||
(items_from_links('themes') + items_from_codes('themes')).uniq.sort.each do |slug|
|
(items_from_links('themes') + items_from_codes('themes')).uniq.sort.each do |slug|
|
||||||
found << WPScan::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'timthumb_version/bad_request'
|
require_relative 'timthumb_version/bad_request'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
@@ -7,7 +9,7 @@ module WPScan
|
|||||||
class Base
|
class Base
|
||||||
include CMSScanner::Finders::UniqueFinder
|
include CMSScanner::Finders::UniqueFinder
|
||||||
|
|
||||||
# @param [ WPScan::Timthumb ] target
|
# @param [ Model::Timthumb ] target
|
||||||
def initialize(target)
|
def initialize(target)
|
||||||
finders << TimthumbVersion::BadRequest.new(target)
|
finders << TimthumbVersion::BadRequest.new(target)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module TimthumbVersion
|
module TimthumbVersion
|
||||||
@@ -8,7 +10,7 @@ module WPScan
|
|||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
return unless Browser.get(target.url).body =~ /(TimThumb version\s*: ([^<]+))/
|
return unless Browser.get(target.url).body =~ /(TimThumb version\s*: ([^<]+))/
|
||||||
|
|
||||||
WPScan::Version.new(
|
Model::Version.new(
|
||||||
Regexp.last_match[2],
|
Regexp.last_match[2],
|
||||||
found_by: 'Bad Request (Aggressive Detection)',
|
found_by: 'Bad Request (Aggressive Detection)',
|
||||||
confidence: 90,
|
confidence: 90,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'timthumbs/known_locations'
|
require_relative 'timthumbs/known_locations'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Timthumbs
|
module Timthumbs
|
||||||
# Known Locations Timthumbs Finder
|
# Known Locations Timthumbs Finder
|
||||||
|
# Note: A vulnerable version, 2.8.13 can be found here:
|
||||||
|
# https://github.com/GabrielGil/TimThumb/blob/980c3d6a823477761570475e8b83d3e9fcd2d7ae/timthumb.php
|
||||||
class KnownLocations < CMSScanner::Finders::Finder
|
class KnownLocations < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
|
# @return [ Array<Integer> ]
|
||||||
|
def valid_response_codes
|
||||||
|
@valid_response_codes ||= [400]
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ String ] :list Mandatory
|
# @option opts [ String ] :list Mandatory
|
||||||
#
|
#
|
||||||
@@ -12,10 +21,10 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts) do |res|
|
enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res|
|
||||||
next unless res.code == 400 && res.body =~ /no image specified/i
|
next unless /no image specified/i.match?(res.body)
|
||||||
|
|
||||||
found << WPScan::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
|
found << Model::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'users/author_posts'
|
require_relative 'users/author_posts'
|
||||||
require_relative 'users/wp_json_api'
|
require_relative 'users/wp_json_api'
|
||||||
require_relative 'users/oembed_api'
|
require_relative 'users/oembed_api'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -5,6 +7,11 @@ module WPScan
|
|||||||
class AuthorIdBruteForcing < CMSScanner::Finders::Finder
|
class AuthorIdBruteForcing < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
|
# @return [ Array<Integer> ]
|
||||||
|
def valid_response_codes
|
||||||
|
@valid_response_codes ||= [200, 301, 302]
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ Range ] :range Mandatory
|
# @option opts [ Range ] :range Mandatory
|
||||||
#
|
#
|
||||||
@@ -13,12 +20,12 @@ module WPScan
|
|||||||
found = []
|
found = []
|
||||||
found_by_msg = 'Author Id Brute Forcing - %s (Aggressive Detection)'
|
found_by_msg = 'Author Id Brute Forcing - %s (Aggressive Detection)'
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts) do |res, id|
|
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, id|
|
||||||
username, found_by, confidence = potential_username(res)
|
username, found_by, confidence = potential_username(res)
|
||||||
|
|
||||||
next unless username
|
next unless username
|
||||||
|
|
||||||
found << CMSScanner::User.new(
|
found << Model::User.new(
|
||||||
username,
|
username,
|
||||||
id: id,
|
id: id,
|
||||||
found_by: format(found_by_msg, found_by),
|
found_by: format(found_by_msg, found_by),
|
||||||
@@ -47,7 +54,7 @@ module WPScan
|
|||||||
super(opts.merge(title: ' Brute Forcing Author IDs -'))
|
super(opts.merge(title: ' Brute Forcing Author IDs -'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def request_params
|
def full_request_params
|
||||||
{ followlocation: true }
|
{ followlocation: true }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -76,8 +83,8 @@ module WPScan
|
|||||||
# @return [ String, nil ] The username found
|
# @return [ String, nil ] The username found
|
||||||
def username_from_response(res)
|
def username_from_response(res)
|
||||||
# Permalink enabled
|
# Permalink enabled
|
||||||
target.in_scope_urls(res, '//link/@href|//a/@href') do |url|
|
target.in_scope_uris(res, '//link/@href|//a/@href') do |uri|
|
||||||
username = username_from_author_url(url)
|
username = username_from_author_url(uri.to_s)
|
||||||
return username if username
|
return username if username
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -10,7 +12,7 @@ module WPScan
|
|||||||
found_by_msg = 'Author Posts - %s (Passive Detection)'
|
found_by_msg = 'Author Posts - %s (Passive Detection)'
|
||||||
|
|
||||||
usernames(opts).reduce([]) do |a, e|
|
usernames(opts).reduce([]) do |a, e|
|
||||||
a << CMSScanner::User.new(
|
a << Model::User.new(
|
||||||
e[0],
|
e[0],
|
||||||
found_by: format(found_by_msg, e[1]),
|
found_by: format(found_by_msg, e[1]),
|
||||||
confidence: e[2]
|
confidence: e[2]
|
||||||
@@ -43,12 +45,10 @@ module WPScan
|
|||||||
def potential_usernames(res)
|
def potential_usernames(res)
|
||||||
usernames = []
|
usernames = []
|
||||||
|
|
||||||
target.in_scope_urls(res, '//a/@href') do |url, node|
|
target.in_scope_uris(res, '//a/@href') do |uri, node|
|
||||||
uri = Addressable::URI.parse(url)
|
|
||||||
|
|
||||||
if uri.path =~ %r{/author/([^/\b]+)/?\z}i
|
if uri.path =~ %r{/author/([^/\b]+)/?\z}i
|
||||||
usernames << [Regexp.last_match[1], 'Author Pattern', 100]
|
usernames << [Regexp.last_match[1], 'Author Pattern', 100]
|
||||||
elsif uri.query =~ /author=[0-9]+/
|
elsif /author=[0-9]+/.match?(uri.query)
|
||||||
usernames << [node.text.to_s.strip, 'Display Name', 30]
|
usernames << [node.text.to_s.strip, 'Display Name', 30]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -22,9 +24,9 @@ module WPScan
|
|||||||
|
|
||||||
return found if error.empty? # Protection plugin / error disabled
|
return found if error.empty? # Protection plugin / error disabled
|
||||||
|
|
||||||
next unless error =~ /The password you entered for the username|Incorrect Password/i
|
next unless /The password you entered for the username|Incorrect Password/i.match?(error)
|
||||||
|
|
||||||
found << CMSScanner::User.new(username, found_by: found_by, confidence: 100)
|
found << Model::User.new(username, found_by: found_by, confidence: 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -21,10 +23,10 @@ module WPScan
|
|||||||
|
|
||||||
return [] unless details
|
return [] unless details
|
||||||
|
|
||||||
[CMSScanner::User.new(details[0],
|
[Model::User.new(details[0],
|
||||||
found_by: format(found_by_msg, details[1]),
|
found_by: format(found_by_msg, details[1]),
|
||||||
confidence: details[2],
|
confidence: details[2],
|
||||||
interesting_entries: [api_url])]
|
interesting_entries: [api_url])]
|
||||||
rescue JSON::ParserError
|
rescue JSON::ParserError
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
# Users disclosed from the dc:creator field in the RSS
|
# Users disclosed from the dc:creator field in the RSS
|
||||||
# The names disclosed are display names, however depending on the configuration of the blog,
|
# The names disclosed are display names, however depending on the configuration of the blog,
|
||||||
# they can be the same than usernames
|
# they can be the same than usernames
|
||||||
class RSSGenerator < WPScan::Finders::WpVersion::RSSGenerator
|
class RSSGenerator < Finders::WpVersion::RSSGenerator
|
||||||
def process_urls(urls, _opts = {})
|
def process_urls(urls, _opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
@@ -17,20 +19,20 @@ module WPScan
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
res.xml.xpath('//item/dc:creator').each do |node|
|
res.xml.xpath('//item/dc:creator').each do |node|
|
||||||
potential_username = node.text.to_s
|
username = node.text.to_s
|
||||||
|
|
||||||
# Ignoring potential username longer than 60 characters and containing accents
|
# Ignoring potential username longer than 60 characters and containing accents
|
||||||
# as they are considered invalid. See https://github.com/wpscanteam/wpscan/issues/1215
|
# as they are considered invalid. See https://github.com/wpscanteam/wpscan/issues/1215
|
||||||
next if potential_username.length > 60 || potential_username =~ /[^\x00-\x7F]/
|
next if username.strip.empty? || username.length > 60 || username =~ /[^\x00-\x7F]/
|
||||||
|
|
||||||
potential_usernames << potential_username
|
potential_usernames << username
|
||||||
end
|
end
|
||||||
rescue Nokogiri::XML::XPath::SyntaxError
|
rescue Nokogiri::XML::XPath::SyntaxError
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
potential_usernames.uniq.each do |potential_username|
|
potential_usernames.uniq.each do |username|
|
||||||
found << CMSScanner::User.new(potential_username, found_by: found_by, confidence: 50)
|
found << Model::User.new(username, found_by: found_by, confidence: 50)
|
||||||
end
|
end
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -41,11 +43,11 @@ module WPScan
|
|||||||
found = []
|
found = []
|
||||||
|
|
||||||
JSON.parse(response.body)&.each do |user|
|
JSON.parse(response.body)&.each do |user|
|
||||||
found << CMSScanner::User.new(user['slug'],
|
found << Model::User.new(user['slug'],
|
||||||
id: user['id'],
|
id: user['id'],
|
||||||
found_by: found_by,
|
found_by: found_by,
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
interesting_entries: [response.effective_url])
|
interesting_entries: [response.effective_url])
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
@@ -53,7 +55,13 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ] The URL of the API listing the Users
|
# @return [ String ] The URL of the API listing the Users
|
||||||
def api_url
|
def api_url
|
||||||
@api_url ||= target.url('wp-json/wp/v2/users/')
|
return @api_url if @api_url
|
||||||
|
|
||||||
|
target.in_scope_uris(target.homepage_res, "//link[@rel='https://api.w.org/']/@href").each do |uri|
|
||||||
|
return @api_url = uri.join('wp/v2/users/').to_s if uri.path.include?('wp-json')
|
||||||
|
end
|
||||||
|
|
||||||
|
@api_url = target.url('wp-json/wp/v2/users/')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -15,10 +17,10 @@ module WPScan
|
|||||||
|
|
||||||
next unless username && !username.strip.empty?
|
next unless username && !username.strip.empty?
|
||||||
|
|
||||||
found << CMSScanner::User.new(username,
|
found << Model::User.new(username,
|
||||||
found_by: found_by,
|
found_by: found_by,
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
interesting_entries: [sitemap_url])
|
interesting_entries: [sitemap_url])
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'wp_items/urls_in_homepage'
|
require_relative 'wp_items/urls_in_homepage'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpItems
|
module WpItems
|
||||||
@@ -10,8 +12,8 @@ module WPScan
|
|||||||
def items_from_links(type, uniq = true)
|
def items_from_links(type, uniq = true)
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
target.in_scope_urls(target.homepage_res) do |url|
|
target.in_scope_uris(target.homepage_res) do |uri|
|
||||||
next unless url =~ item_attribute_pattern(type)
|
next unless uri.to_s =~ item_attribute_pattern(type)
|
||||||
|
|
||||||
found << Regexp.last_match[1]
|
found << Regexp.last_match[1]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'wp_version/rss_generator'
|
require_relative 'wp_version/rss_generator'
|
||||||
require_relative 'wp_version/atom_generator'
|
require_relative 'wp_version/atom_generator'
|
||||||
require_relative 'wp_version/rdf_generator'
|
require_relative 'wp_version/rdf_generator'
|
||||||
@@ -26,7 +28,7 @@ module WPScan
|
|||||||
# @param [ WPScan::Target ] target
|
# @param [ WPScan::Target ] target
|
||||||
def initialize(target)
|
def initialize(target)
|
||||||
(%w[RSSGenerator AtomGenerator RDFGenerator] +
|
(%w[RSSGenerator AtomGenerator RDFGenerator] +
|
||||||
WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
|
DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
|
||||||
%w[Readme UniqueFingerprinting]
|
%w[Readme UniqueFingerprinting]
|
||||||
).each do |finder_name|
|
).each do |finder_name|
|
||||||
finders << WpVersion.const_get(finder_name.to_sym).new(target)
|
finders << WpVersion.const_get(finder_name.to_sym).new(target)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpVersion
|
module WpVersion
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpVersion
|
module WpVersion
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpVersion
|
module WpVersion
|
||||||
@@ -13,9 +15,9 @@ module WPScan
|
|||||||
|
|
||||||
number = Regexp.last_match(1)
|
number = Regexp.last_match(1)
|
||||||
|
|
||||||
return unless WPScan::WpVersion.valid?(number)
|
return unless Model::WpVersion.valid?(number)
|
||||||
|
|
||||||
WPScan::WpVersion.new(
|
Model::WpVersion.new(
|
||||||
number,
|
number,
|
||||||
found_by: 'Readme (Aggressive Detection)',
|
found_by: 'Readme (Aggressive Detection)',
|
||||||
# Since WP 4.7, the Readme only contains the major version (ie 4.7, 4.8 etc)
|
# Since WP 4.7, the Readme only contains the major version (ie 4.7, 4.8 etc)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpVersion
|
module WpVersion
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpVersion
|
module WpVersion
|
||||||
@@ -11,7 +13,7 @@ module WPScan
|
|||||||
hydra.abort
|
hydra.abort
|
||||||
progress_bar.finish
|
progress_bar.finish
|
||||||
|
|
||||||
return WPScan::WpVersion.new(
|
return Model::WpVersion.new(
|
||||||
version_number,
|
version_number,
|
||||||
found_by: 'Unique Fingerprinting (Aggressive Detection)',
|
found_by: 'Unique Fingerprinting (Aggressive Detection)',
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module Model
|
||||||
|
include CMSScanner::Model
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
require_relative 'models/interesting_finding'
|
require_relative 'models/interesting_finding'
|
||||||
require_relative 'models/wp_version'
|
require_relative 'models/wp_version'
|
||||||
require_relative 'models/xml_rpc'
|
require_relative 'models/xml_rpc'
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
# Config Backup
|
module Model
|
||||||
class ConfigBackup < InterestingFinding
|
# Config Backup
|
||||||
|
class ConfigBackup < InterestingFinding
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
# DB Export
|
module Model
|
||||||
class DbExport < InterestingFinding
|
# DB Export
|
||||||
|
class DbExport < InterestingFinding
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,48 +1,52 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
# Custom class to include the WPScan::References module
|
module Model
|
||||||
class InterestingFinding < CMSScanner::InterestingFinding
|
# Custom class to include the WPScan::References module
|
||||||
include References
|
class InterestingFinding < CMSScanner::Model::InterestingFinding
|
||||||
end
|
include References
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Empty classes for the #type to be correctly displayed (as taken from the self.class from the parent)
|
# Empty classes for the #type to be correctly displayed (as taken from the self.class from the parent)
|
||||||
#
|
#
|
||||||
class BackupDB < InterestingFinding
|
class BackupDB < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class DebugLog < InterestingFinding
|
class DebugLog < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class DuplicatorInstallerLog < InterestingFinding
|
class DuplicatorInstallerLog < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class EmergencyPwdResetScript < InterestingFinding
|
class EmergencyPwdResetScript < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class FullPathDisclosure < InterestingFinding
|
class FullPathDisclosure < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class MuPlugins < InterestingFinding
|
class MuPlugins < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class Multisite < InterestingFinding
|
class Multisite < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class Readme < InterestingFinding
|
class Readme < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class Registration < InterestingFinding
|
class Registration < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class TmmDbMigrate < InterestingFinding
|
class TmmDbMigrate < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class UploadDirectoryListing < InterestingFinding
|
class UploadDirectoryListing < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class UploadSQLDump < InterestingFinding
|
class UploadSQLDump < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class WPCron < InterestingFinding
|
class WPCron < InterestingFinding
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
# Media
|
module Model
|
||||||
class Media < InterestingFinding
|
# Media
|
||||||
|
class Media < InterestingFinding
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,25 +1,38 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
# WordPress Plugin
|
module Model
|
||||||
class Plugin < WpItem
|
# WordPress Plugin
|
||||||
# See WpItem
|
class Plugin < WpItem
|
||||||
def initialize(slug, blog, opts = {})
|
# See WpItem
|
||||||
super(slug, blog, opts)
|
def initialize(slug, blog, opts = {})
|
||||||
|
super(slug, blog, opts)
|
||||||
|
|
||||||
@uri = Addressable::URI.parse(blog.url("wp-content/plugins/#{slug}/"))
|
# To be used by #head_and_get
|
||||||
end
|
# If custom wp-content, it will be replaced by blog#url
|
||||||
|
@path_from_blog = "wp-content/plugins/#{slug}/"
|
||||||
|
|
||||||
# @return [ JSON ]
|
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
||||||
def db_data
|
end
|
||||||
DB::Plugin.db_data(slug)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @return [ JSON ]
|
||||||
#
|
def db_data
|
||||||
# @return [ WPScan::Version, false ]
|
@db_data ||= DB::Plugin.db_data(slug)
|
||||||
def version(opts = {})
|
end
|
||||||
@version = Finders::PluginVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
|
||||||
|
|
||||||
@version
|
# @param [ Hash ] opts
|
||||||
|
#
|
||||||
|
# @return [ Model::Version, false ]
|
||||||
|
def version(opts = {})
|
||||||
|
@version = Finders::PluginVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
||||||
|
|
||||||
|
@version
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Array<String> ]
|
||||||
|
def potential_readme_filenames
|
||||||
|
@potential_readme_filenames ||= [*(DB::DynamicFinders::Plugin.df_data.dig(slug, 'Readme', 'path') || super)]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,99 +1,107 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
# WordPress Theme
|
module Model
|
||||||
class Theme < WpItem
|
# WordPress Theme
|
||||||
attr_reader :style_url, :style_name, :style_uri, :author, :author_uri, :template, :description,
|
class Theme < WpItem
|
||||||
:license, :license_uri, :tags, :text_domain
|
attr_reader :style_url, :style_name, :style_uri, :author, :author_uri, :template, :description,
|
||||||
|
:license, :license_uri, :tags, :text_domain
|
||||||
|
|
||||||
# See WpItem
|
# See WpItem
|
||||||
def initialize(slug, blog, opts = {})
|
def initialize(slug, blog, opts = {})
|
||||||
super(slug, blog, opts)
|
super(slug, blog, opts)
|
||||||
|
|
||||||
@uri = Addressable::URI.parse(blog.url("wp-content/themes/#{slug}/"))
|
# To be used by #head_and_get
|
||||||
@style_url = opts[:style_url] || url('style.css')
|
# If custom wp-content, it will be replaced by blog#url
|
||||||
|
@path_from_blog = "wp-content/themes/#{slug}/"
|
||||||
|
|
||||||
parse_style
|
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
||||||
end
|
@style_url = opts[:style_url] || url('style.css')
|
||||||
|
|
||||||
# @return [ JSON ]
|
parse_style
|
||||||
def db_data
|
|
||||||
DB::Theme.db_data(slug)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
|
||||||
#
|
|
||||||
# @return [ WPScan::Version, false ]
|
|
||||||
def version(opts = {})
|
|
||||||
@version = Finders::ThemeVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
|
||||||
|
|
||||||
@version
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ Theme ]
|
|
||||||
def parent_theme
|
|
||||||
return unless template
|
|
||||||
return unless style_body =~ /^@import\surl\(["']?([^"'\)]+)["']?\);\s*$/i
|
|
||||||
|
|
||||||
opts = detection_opts.merge(
|
|
||||||
style_url: url(Regexp.last_match[1]),
|
|
||||||
found_by: 'Parent Themes (Passive Detection)',
|
|
||||||
confidence: 100
|
|
||||||
).merge(version_detection: version_detection_opts)
|
|
||||||
|
|
||||||
self.class.new(template, blog, opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [ Integer ] depth
|
|
||||||
#
|
|
||||||
# @retun [ Array<Theme> ]
|
|
||||||
def parent_themes(depth = 3)
|
|
||||||
theme = self
|
|
||||||
found = []
|
|
||||||
|
|
||||||
(1..depth).each do |_|
|
|
||||||
parent = theme.parent_theme
|
|
||||||
|
|
||||||
break unless parent
|
|
||||||
|
|
||||||
found << parent
|
|
||||||
theme = parent
|
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
# @return [ JSON ]
|
||||||
end
|
def db_data
|
||||||
|
@db_data ||= DB::Theme.db_data(slug)
|
||||||
def style_body
|
|
||||||
@style_body ||= Browser.get(style_url).body
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_style
|
|
||||||
{
|
|
||||||
style_name: 'Theme Name',
|
|
||||||
style_uri: 'Theme URI',
|
|
||||||
author: 'Author',
|
|
||||||
author_uri: 'Author URI',
|
|
||||||
template: 'Template',
|
|
||||||
description: 'Description',
|
|
||||||
license: 'License',
|
|
||||||
license_uri: 'License URI',
|
|
||||||
tags: 'Tags',
|
|
||||||
text_domain: 'Text Domain'
|
|
||||||
}.each do |attribute, tag|
|
|
||||||
instance_variable_set(:"@#{attribute}", parse_style_tag(style_body, tag))
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# @param [ String ] bofy
|
# @param [ Hash ] opts
|
||||||
# @param [ String ] tag
|
#
|
||||||
#
|
# @return [ Model::Version, false ]
|
||||||
# @return [ String ]
|
def version(opts = {})
|
||||||
def parse_style_tag(body, tag)
|
@version = Finders::ThemeVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
||||||
value = body[/^\s*#{Regexp.escape(tag)}:[\t ]*([^\r\n]+)/i, 1]
|
|
||||||
|
|
||||||
value && !value.strip.empty? ? value.strip : nil
|
@version
|
||||||
end
|
end
|
||||||
|
|
||||||
def ==(other)
|
# @return [ Theme ]
|
||||||
super(other) && style_url == other.style_url
|
def parent_theme
|
||||||
|
return unless template
|
||||||
|
return unless style_body =~ /^@import\surl\(["']?([^"'\)]+)["']?\);\s*$/i
|
||||||
|
|
||||||
|
opts = detection_opts.merge(
|
||||||
|
style_url: url(Regexp.last_match[1]),
|
||||||
|
found_by: 'Parent Themes (Passive Detection)',
|
||||||
|
confidence: 100
|
||||||
|
).merge(version_detection: version_detection_opts)
|
||||||
|
|
||||||
|
self.class.new(template, blog, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [ Integer ] depth
|
||||||
|
#
|
||||||
|
# @retun [ Array<Theme> ]
|
||||||
|
def parent_themes(depth = 3)
|
||||||
|
theme = self
|
||||||
|
found = []
|
||||||
|
|
||||||
|
(1..depth).each do |_|
|
||||||
|
parent = theme.parent_theme
|
||||||
|
|
||||||
|
break unless parent
|
||||||
|
|
||||||
|
found << parent
|
||||||
|
theme = parent
|
||||||
|
end
|
||||||
|
|
||||||
|
found
|
||||||
|
end
|
||||||
|
|
||||||
|
def style_body
|
||||||
|
@style_body ||= Browser.get(style_url).body
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_style
|
||||||
|
{
|
||||||
|
style_name: 'Theme Name',
|
||||||
|
style_uri: 'Theme URI',
|
||||||
|
author: 'Author',
|
||||||
|
author_uri: 'Author URI',
|
||||||
|
template: 'Template',
|
||||||
|
description: 'Description',
|
||||||
|
license: 'License',
|
||||||
|
license_uri: 'License URI',
|
||||||
|
tags: 'Tags',
|
||||||
|
text_domain: 'Text Domain'
|
||||||
|
}.each do |attribute, tag|
|
||||||
|
instance_variable_set(:"@#{attribute}", parse_style_tag(style_body, tag))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [ String ] bofy
|
||||||
|
# @param [ String ] tag
|
||||||
|
#
|
||||||
|
# @return [ String ]
|
||||||
|
def parse_style_tag(body, tag)
|
||||||
|
value = body[/^\s*#{Regexp.escape(tag)}:[\t ]*([^\r\n]+)/i, 1]
|
||||||
|
|
||||||
|
value && !value.strip.empty? ? value.strip : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
super(other) && style_url == other.style_url
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,71 +1,75 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
# Timthumb
|
module Model
|
||||||
class Timthumb < InterestingFinding
|
# Timthumb
|
||||||
include Vulnerable
|
class Timthumb < InterestingFinding
|
||||||
|
include Vulnerable
|
||||||
|
|
||||||
attr_reader :version_detection_opts
|
attr_reader :version_detection_opts
|
||||||
|
|
||||||
# @param [ String ] url
|
# @param [ String ] url
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ Symbol ] :mode The mode to use to detect the version
|
# @option opts [ Symbol ] :mode The mode to use to detect the version
|
||||||
def initialize(url, opts = {})
|
def initialize(url, opts = {})
|
||||||
super(url, opts)
|
super(url, opts)
|
||||||
|
|
||||||
@version_detection_opts = opts[:version_detection] || {}
|
@version_detection_opts = opts[:version_detection] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
#
|
#
|
||||||
# @return [ WPScan::Version, false ]
|
# @return [ Model::Version, false ]
|
||||||
def version(opts = {})
|
def version(opts = {})
|
||||||
@version = Finders::TimthumbVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
@version = Finders::TimthumbVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
||||||
|
|
||||||
@version
|
@version
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<Vulnerability> ]
|
# @return [ Array<Vulnerability> ]
|
||||||
def vulnerabilities
|
def vulnerabilities
|
||||||
vulns = []
|
vulns = []
|
||||||
|
|
||||||
vulns << rce_webshot_vuln if version == false || version > '1.35' && version < '2.8.14' && webshot_enabled?
|
vulns << rce_webshot_vuln if version == false || version > '1.35' && version < '2.8.14' && webshot_enabled?
|
||||||
vulns << rce_132_vuln if version == false || version < '1.33'
|
vulns << rce_132_vuln if version == false || version < '1.33'
|
||||||
|
|
||||||
vulns
|
vulns
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Vulnerability ] The RCE in the <= 1.32
|
# @return [ Vulnerability ] The RCE in the <= 1.32
|
||||||
def rce_132_vuln
|
def rce_132_vuln
|
||||||
Vulnerability.new(
|
Vulnerability.new(
|
||||||
'Timthumb <= 1.32 Remote Code Execution',
|
'Timthumb <= 1.32 Remote Code Execution',
|
||||||
{ exploitdb: ['17602'] },
|
{ exploitdb: ['17602'] },
|
||||||
'RCE',
|
'RCE',
|
||||||
'1.33'
|
'1.33'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Vulnerability ] The RCE due to the WebShot in the > 1.35 (or >= 2.0) and <= 2.8.13
|
# @return [ Vulnerability ] The RCE due to the WebShot in the > 1.35 (or >= 2.0) and <= 2.8.13
|
||||||
def rce_webshot_vuln
|
def rce_webshot_vuln
|
||||||
Vulnerability.new(
|
Vulnerability.new(
|
||||||
'Timthumb <= 2.8.13 WebShot Remote Code Execution',
|
'Timthumb <= 2.8.13 WebShot Remote Code Execution',
|
||||||
{
|
{
|
||||||
url: ['http://seclists.org/fulldisclosure/2014/Jun/117', 'https://github.com/wpscanteam/wpscan/issues/519'],
|
url: ['http://seclists.org/fulldisclosure/2014/Jun/117', 'https://github.com/wpscanteam/wpscan/issues/519'],
|
||||||
cve: '2014-4663'
|
cve: '2014-4663'
|
||||||
},
|
},
|
||||||
'RCE',
|
'RCE',
|
||||||
'2.8.14'
|
'2.8.14'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def webshot_enabled?
|
def webshot_enabled?
|
||||||
res = Browser.get(url, params: { webshot: 1, src: "http://#{default_allowed_domains.sample}" })
|
res = Browser.get(url, params: { webshot: 1, src: "http://#{default_allowed_domains.sample}" })
|
||||||
|
|
||||||
res.body =~ /WEBSHOT_ENABLED == true/ ? false : true
|
/WEBSHOT_ENABLED == true/.match?(res.body) ? false : true
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<String> ] The default allowed domains (between the 2.0 and 2.8.13)
|
# @return [ Array<String> ] The default allowed domains (between the 2.0 and 2.8.13)
|
||||||
def default_allowed_domains
|
def default_allowed_domains
|
||||||
%w[flickr.com picasa.com img.youtube.com upload.wikimedia.org]
|
%w[flickr.com picasa.com img.youtube.com upload.wikimedia.org]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,158 +1,175 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
# WpItem (superclass of Plugin & Theme)
|
module Model
|
||||||
class WpItem
|
# WpItem (superclass of Plugin & Theme)
|
||||||
include Vulnerable
|
class WpItem
|
||||||
include Finders::Finding
|
include Vulnerable
|
||||||
include CMSScanner::Target::Platform::PHP
|
include Finders::Finding
|
||||||
include CMSScanner::Target::Server::Generic
|
include CMSScanner::Target::Platform::PHP
|
||||||
|
include CMSScanner::Target::Server::Generic
|
||||||
|
|
||||||
READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
|
# Most common readme filenames, based on checking all public plugins and themes.
|
||||||
CHANGELOGS = %w[changelog.txt CHANGELOG.md changelog.md].freeze
|
READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
|
||||||
|
|
||||||
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :db_data
|
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
||||||
|
|
||||||
delegate :homepage_res, :xpath_pattern_from_page, :in_scope_urls, to: :blog
|
delegate :homepage_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
|
||||||
|
|
||||||
# @param [ String ] slug The plugin/theme slug
|
# @param [ String ] slug The plugin/theme slug
|
||||||
# @param [ Target ] blog The targeted blog
|
# @param [ Target ] blog The targeted blog
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ Symbol ] :mode The detection mode to use
|
# @option opts [ Symbol ] :mode The detection mode to use
|
||||||
# @option opts [ Hash ] :version_detection The options to use when looking for the version
|
# @option opts [ Hash ] :version_detection The options to use when looking for the version
|
||||||
# @option opts [ String ] :url The URL of the item
|
# @option opts [ String ] :url The URL of the item
|
||||||
def initialize(slug, blog, opts = {})
|
def initialize(slug, blog, opts = {})
|
||||||
@slug = URI.decode(slug)
|
@slug = URI.decode(slug)
|
||||||
@blog = blog
|
@blog = blog
|
||||||
@uri = Addressable::URI.parse(opts[:url]) if opts[:url]
|
@uri = Addressable::URI.parse(opts[:url]) if opts[:url]
|
||||||
|
|
||||||
@detection_opts = { mode: opts[:mode] }
|
@detection_opts = { mode: opts[:mode] }
|
||||||
@version_detection_opts = opts[:version_detection] || {}
|
@version_detection_opts = opts[:version_detection] || {}
|
||||||
|
|
||||||
parse_finding_options(opts)
|
parse_finding_options(opts)
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ Array<Vulnerabily> ]
|
|
||||||
def vulnerabilities
|
|
||||||
return @vulnerabilities if @vulnerabilities
|
|
||||||
|
|
||||||
@vulnerabilities = []
|
|
||||||
|
|
||||||
[*db_data['vulnerabilities']].each do |json_vuln|
|
|
||||||
vulnerability = Vulnerability.load_from_json(json_vuln)
|
|
||||||
@vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@vulnerabilities
|
# @return [ Array<Vulnerabily> ]
|
||||||
end
|
def vulnerabilities
|
||||||
|
return @vulnerabilities if @vulnerabilities
|
||||||
|
|
||||||
# Checks if the wp_item is vulnerable to a specific vulnerability
|
@vulnerabilities = []
|
||||||
#
|
|
||||||
# @param [ Vulnerability ] vuln Vulnerability to check the item against
|
|
||||||
#
|
|
||||||
# @return [ Boolean ]
|
|
||||||
def vulnerable_to?(vuln)
|
|
||||||
return true unless version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
|
|
||||||
|
|
||||||
version < vuln.fixed_in
|
[*db_data['vulnerabilities']].each do |json_vuln|
|
||||||
end
|
vulnerability = Vulnerability.load_from_json(json_vuln)
|
||||||
|
@vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
||||||
# @return [ String ]
|
|
||||||
def latest_version
|
|
||||||
@latest_version ||= db_data['latest_version'] ? WPScan::Version.new(db_data['latest_version']) : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Not used anywhere ATM
|
|
||||||
# @return [ Boolean ]
|
|
||||||
def popular?
|
|
||||||
@popular ||= db_data['popular']
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ String ]
|
|
||||||
def last_updated
|
|
||||||
@last_updated ||= db_data['last_updated']
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ Boolean ]
|
|
||||||
def outdated?
|
|
||||||
@outdated ||= if version && latest_version
|
|
||||||
version < latest_version
|
|
||||||
else
|
|
||||||
false
|
|
||||||
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 ]
|
|
||||||
def url(path = nil)
|
|
||||||
return unless @uri
|
|
||||||
return @uri.to_s unless path
|
|
||||||
|
|
||||||
@uri.join(URI.encode(path)).to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ Boolean ]
|
|
||||||
def ==(other)
|
|
||||||
self.class == other.class && slug == other.slug
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
slug
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ Symbol ] The Class symbol associated to the item
|
|
||||||
def classify
|
|
||||||
@classify ||= classify_slug(slug)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ String ] The readme url if found
|
|
||||||
def readme_url
|
|
||||||
return if detection_opts[:mode] == :passive
|
|
||||||
|
|
||||||
if @readme_url.nil?
|
|
||||||
READMES.each do |path|
|
|
||||||
return @readme_url = url(path) if Browser.get(url(path)).code == 200
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@vulnerabilities
|
||||||
end
|
end
|
||||||
|
|
||||||
@readme_url
|
# Checks if the wp_item is vulnerable to a specific vulnerability
|
||||||
end
|
#
|
||||||
|
# @param [ Vulnerability ] vuln Vulnerability to check the item against
|
||||||
|
#
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def vulnerable_to?(vuln)
|
||||||
|
return true unless version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
|
||||||
|
|
||||||
# @return [ String, false ] The changelog urr if found
|
version < vuln.fixed_in
|
||||||
def changelog_url
|
end
|
||||||
return if detection_opts[:mode] == :passive
|
|
||||||
|
|
||||||
if @changelog_url.nil?
|
# @return [ String ]
|
||||||
CHANGELOGS.each do |path|
|
def latest_version
|
||||||
return @changelog_url = url(path) if Browser.get(url(path)).code == 200
|
@latest_version ||= db_data['latest_version'] ? Model::Version.new(db_data['latest_version']) : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Not used anywhere ATM
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def popular?
|
||||||
|
@popular ||= db_data['popular']
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def last_updated
|
||||||
|
@last_updated ||= db_data['last_updated']
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def outdated?
|
||||||
|
@outdated ||= if version && latest_version
|
||||||
|
version < latest_version
|
||||||
|
else
|
||||||
|
false
|
||||||
|
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 ]
|
||||||
|
def url(path = nil)
|
||||||
|
return unless @uri
|
||||||
|
return @uri.to_s unless path
|
||||||
|
|
||||||
|
@uri.join(URI.encode(path)).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def ==(other)
|
||||||
|
self.class == other.class && slug == other.slug
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
slug
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Symbol ] The Class symbol associated to the item
|
||||||
|
def classify
|
||||||
|
@classify ||= classify_slug(slug)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ String, False ] The readme url if found, false otherwise
|
||||||
|
def readme_url
|
||||||
|
return if detection_opts[:mode] == :passive
|
||||||
|
|
||||||
|
return @readme_url unless @readme_url.nil?
|
||||||
|
|
||||||
|
potential_readme_filenames.each do |path|
|
||||||
|
t_url = url(path)
|
||||||
|
|
||||||
|
return @readme_url = t_url if Browser.forge_request(t_url, blog.head_or_get_params).run.code == 200
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@readme_url = false
|
||||||
end
|
end
|
||||||
|
|
||||||
@changelog_url
|
def potential_readme_filenames
|
||||||
end
|
@potential_readme_filenames ||= READMES
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ String ] path
|
# @param [ String ] path
|
||||||
# @param [ Hash ] params The request params
|
# @param [ Hash ] params The request params
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def directory_listing?(path = nil, params = {})
|
def directory_listing?(path = nil, params = {})
|
||||||
return if detection_opts[:mode] == :passive
|
return if detection_opts[:mode] == :passive
|
||||||
|
|
||||||
super(path, params)
|
super(path, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ String ] path
|
# @param [ String ] path
|
||||||
# @param [ Hash ] params The request params
|
# @param [ Hash ] params The request params
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def error_log?(path = 'error_log', params = {})
|
def error_log?(path = 'error_log', params = {})
|
||||||
return if detection_opts[:mode] == :passive
|
return if detection_opts[:mode] == :passive
|
||||||
|
|
||||||
super(path, params)
|
super(path, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
# See CMSScanner::Target#head_and_get
|
||||||
|
#
|
||||||
|
# This is used by the error_log? above in the super()
|
||||||
|
# to have the correct path (ie readme.txt checked from the plugin/theme location
|
||||||
|
# and not from the blog root). Could also be used in finders
|
||||||
|
#
|
||||||
|
# @param [ String ] path
|
||||||
|
# @param [ Array<String> ] codes
|
||||||
|
# @param [ Hash ] params The requests params
|
||||||
|
# @option params [ Hash ] :head Request params for the HEAD
|
||||||
|
# @option params [ hash ] :get Request params for the GET
|
||||||
|
#
|
||||||
|
# @return [ Typhoeus::Response ]
|
||||||
|
def head_and_get(path, codes = [200], params = {})
|
||||||
|
final_path = +@path_from_blog
|
||||||
|
final_path << URI.encode(path) unless path.nil?
|
||||||
|
|
||||||
|
blog.head_and_get(final_path, codes, params)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,64 +1,67 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
# WP Version
|
module Model
|
||||||
class WpVersion < CMSScanner::Version
|
# WP Version
|
||||||
include Vulnerable
|
class WpVersion < CMSScanner::Model::Version
|
||||||
|
include Vulnerable
|
||||||
|
|
||||||
def initialize(number, opts = {})
|
def initialize(number, opts = {})
|
||||||
raise InvalidWordPressVersion unless WpVersion.valid?(number.to_s)
|
raise Error::InvalidWordPressVersion unless WpVersion.valid?(number.to_s)
|
||||||
|
|
||||||
super(number, opts)
|
super(number, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ String ] number
|
# @param [ String ] number
|
||||||
#
|
#
|
||||||
# @return [ Boolean ] true if the number is a valid WP version, false otherwise
|
# @return [ Boolean ] true if the number is a valid WP version, false otherwise
|
||||||
def self.valid?(number)
|
def self.valid?(number)
|
||||||
all.include?(number)
|
all.include?(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<String> ] All the version numbers
|
# @return [ Array<String> ] All the version numbers
|
||||||
def self.all
|
def self.all
|
||||||
return @all_numbers if @all_numbers
|
return @all_numbers if @all_numbers
|
||||||
|
|
||||||
@all_numbers = []
|
@all_numbers = []
|
||||||
|
|
||||||
DB::Fingerprints.wp_fingerprints.each_value do |fp|
|
DB::Fingerprints.wp_fingerprints.each_value do |fp|
|
||||||
fp.each_value do |versions|
|
@all_numbers << fp.values
|
||||||
versions.each do |version|
|
|
||||||
@all_numbers << version unless @all_numbers.include?(version)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @all_numbers.flatten.uniq.sort! {} doesn't produce the same result here.
|
||||||
|
@all_numbers.flatten!
|
||||||
|
@all_numbers.uniq!
|
||||||
|
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
||||||
end
|
end
|
||||||
|
|
||||||
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
# @return [ JSON ]
|
||||||
end
|
def db_data
|
||||||
|
@db_data ||= DB::Version.db_data(number)
|
||||||
# @return [ JSON ]
|
|
||||||
def db_data
|
|
||||||
DB::Version.db_data(number)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ Array<Vulnerability> ]
|
|
||||||
def vulnerabilities
|
|
||||||
return @vulnerabilities if @vulnerabilities
|
|
||||||
|
|
||||||
@vulnerabilities = []
|
|
||||||
|
|
||||||
[*db_data['vulnerabilities']].each do |json_vuln|
|
|
||||||
@vulnerabilities << Vulnerability.load_from_json(json_vuln)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@vulnerabilities
|
# @return [ Array<Vulnerability> ]
|
||||||
end
|
def vulnerabilities
|
||||||
|
return @vulnerabilities if @vulnerabilities
|
||||||
|
|
||||||
# @return [ String ]
|
@vulnerabilities = []
|
||||||
def release_date
|
|
||||||
@release_date ||= db_data['release_date'] || 'Unknown'
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ String ]
|
[*db_data['vulnerabilities']].each do |json_vuln|
|
||||||
def status
|
@vulnerabilities << Vulnerability.load_from_json(json_vuln)
|
||||||
@status ||= db_data['status'] || 'Unknown'
|
end
|
||||||
|
|
||||||
|
@vulnerabilities
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def release_date
|
||||||
|
@release_date ||= db_data['release_date'] || 'Unknown'
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def status
|
||||||
|
@status ||= db_data['status'] || 'Unknown'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
module WPScan
|
# frozen_string_literal: true
|
||||||
# Override of the CMSScanner::XMLRPC to include the references
|
|
||||||
class XMLRPC < CMSScanner::XMLRPC
|
|
||||||
include References # To be able to use the :wpvulndb reference if needed
|
|
||||||
|
|
||||||
# @return [ Hash ]
|
module WPScan
|
||||||
def references
|
module Model
|
||||||
{
|
# Override of the CMSScanner::XMLRPC to include the references
|
||||||
url: ['http://codex.wordpress.org/XML-RPC_Pingback_API'],
|
class XMLRPC < CMSScanner::Model::XMLRPC
|
||||||
metasploit: [
|
include References # To be able to use the :wpvulndb reference if needed
|
||||||
'auxiliary/scanner/http/wordpress_ghost_scanner',
|
|
||||||
'auxiliary/dos/http/wordpress_xmlrpc_dos',
|
# @return [ Hash ]
|
||||||
'auxiliary/scanner/http/wordpress_xmlrpc_login',
|
def references
|
||||||
'auxiliary/scanner/http/wordpress_pingback_access'
|
{
|
||||||
]
|
url: ['http://codex.wordpress.org/XML-RPC_Pingback_API'],
|
||||||
}
|
metasploit: [
|
||||||
|
'auxiliary/scanner/http/wordpress_ghost_scanner',
|
||||||
|
'auxiliary/dos/http/wordpress_xmlrpc_dos',
|
||||||
|
'auxiliary/scanner/http/wordpress_xmlrpc_login',
|
||||||
|
'auxiliary/scanner/http/wordpress_pingback_access'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user