Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32b4670755 | ||
|
|
4a032d5e12 | ||
|
|
5887fede15 | ||
|
|
ad4eeb9f81 | ||
|
|
a62c16d7cc | ||
|
|
e766e7392a | ||
|
|
025c9c24ca | ||
|
|
ab052add27 | ||
|
|
15cb99977b | ||
|
|
82d5af926f | ||
|
|
76f73f3dc8 | ||
|
|
575b22320e | ||
|
|
d20c07dc85 | ||
|
|
f89071b87a | ||
|
|
8b4e90f285 | ||
|
|
9c4f57c786 | ||
|
|
902ec24b77 | ||
|
|
7eba77fa63 | ||
|
|
0753bbf7b3 | ||
|
|
6b2333614a | ||
|
|
80b7f458f5 | ||
|
|
dbd8e59cf4 | ||
|
|
9948230ea0 | ||
|
|
e2c858ac69 | ||
|
|
bac8b613e6 | ||
|
|
abbae15c6f | ||
|
|
1548e8bfc1 | ||
|
|
dc8cf3fc34 | ||
|
|
c3cd815567 | ||
|
|
ce543b9384 | ||
|
|
9755c8cf42 | ||
|
|
434a210fb5 | ||
|
|
587602665a | ||
|
|
bfec63df41 | ||
|
|
3b150df1af | ||
|
|
f24ecf0537 | ||
|
|
9ddecbcc0a | ||
|
|
947bb8d3d5 | ||
|
|
30cbf87b35 | ||
|
|
69c3aab35a | ||
|
|
bdeb3547f1 | ||
|
|
99e04b9669 | ||
|
|
680d2fb7eb | ||
|
|
8814eda018 | ||
|
|
7e72ba2885 | ||
|
|
b4d7a8490b | ||
|
|
e9a5bc66df | ||
|
|
edebc77726 | ||
|
|
271dee824d | ||
|
|
1e868d10ca | ||
|
|
4be3f17ae4 | ||
|
|
f24e7be264 | ||
|
|
9adc26445d | ||
|
|
353e7dcbb9 | ||
|
|
430e65c12e | ||
|
|
1aa242a9d8 | ||
|
|
7173cd85fe | ||
|
|
b95a4f55e3 | ||
|
|
6b5e016770 | ||
|
|
85aa9f61cd | ||
|
|
5c187002d6 | ||
|
|
9bc373308b | ||
|
|
cdeb0fc144 | ||
|
|
f1acdd9389 | ||
|
|
d6fac6a210 | ||
|
|
007cfb0801 | ||
|
|
1f9829b7c0 | ||
|
|
e039d22565 | ||
|
|
b0775b1610 | ||
|
|
0e429700c6 | ||
|
|
af7804ca23 | ||
|
|
9da326967b | ||
|
|
62600b3a66 | ||
|
|
b236138fb5 | ||
|
|
40c2e9a54b | ||
|
|
a9062db57f | ||
|
|
2621404c5f | ||
|
|
c47211ca79 | ||
|
|
e39a192e8d | ||
|
|
d85035d5ef | ||
|
|
de09a97343 | ||
|
|
a6855345d7 | ||
|
|
a53f88b626 | ||
|
|
7048c82124 | ||
|
|
6aa7cda478 | ||
|
|
ff339b9a8c | ||
|
|
8898cc20fe | ||
|
|
770d1da280 | ||
|
|
6ba4e8a29b | ||
|
|
953ca68495 | ||
|
|
4289dfb37d | ||
|
|
4f6f2f436a | ||
|
|
237979a479 | ||
|
|
2e48968fd3 | ||
|
|
9a0c4a5c8f | ||
|
|
9a011f0007 | ||
|
|
3f907a706f | ||
|
|
9446141716 | ||
|
|
1994826af8 | ||
|
|
ab950d6ffc | ||
|
|
b77e611a90 | ||
|
|
86f0284894 | ||
|
|
9bbe014dfe | ||
|
|
ad92c95500 | ||
|
|
d360190382 | ||
|
|
1737c8a7f6 | ||
|
|
cde262fd66 | ||
|
|
bd74689079 | ||
|
|
248942bdea | ||
|
|
d9f203300b | ||
|
|
aceabc969f | ||
|
|
dedc24d3a7 | ||
|
|
6e583e78e8 | ||
|
|
c012e83355 | ||
|
|
264355d185 | ||
|
|
fdbfd1ec60 | ||
|
|
7a8b27a255 | ||
|
|
ec4bfac98b | ||
|
|
c63ffe37c9 | ||
|
|
d2f3ce82c9 | ||
|
|
3e24a0b0a4 | ||
|
|
1a07e29ff4 | ||
|
|
1aa46a8928 | ||
|
|
d9083f8b5f | ||
|
|
23d558a6d7 | ||
|
|
665a5b7b12 | ||
|
|
1d73418969 | ||
|
|
f67b5e4cc4 | ||
|
|
ae2515444f | ||
|
|
463e77f0a5 | ||
|
|
d7b796b1a7 | ||
|
|
9b07d53077 | ||
|
|
8ee9b2bc31 | ||
|
|
c5989477a4 | ||
|
|
96d8a4e4f8 | ||
|
|
e865e11731 | ||
|
|
f0997bfe0d | ||
|
|
8b67dad456 | ||
|
|
53fdac1038 | ||
|
|
534a7602e6 | ||
|
|
30f329fe43 | ||
|
|
4ce39951a9 | ||
|
|
0e9eb34626 | ||
|
|
0ff299c425 | ||
|
|
6366258ce9 | ||
|
|
bca69a026e | ||
|
|
adc26ea42a | ||
|
|
84422b10c8 | ||
|
|
d05ad0f8f4 | ||
|
|
3f70ddaffa | ||
|
|
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 |
@@ -14,3 +14,4 @@ Dockerfile
|
||||
*.orig
|
||||
bin/wpscan-*
|
||||
.wpscan/
|
||||
.github/
|
||||
|
||||
40
.github/workflows/gempush.yml
vendored
Normal file
40
.github/workflows/gempush.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Ruby Gem
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build + Publish
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Set up Ruby 2.6
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.6.x
|
||||
|
||||
#- name: Publish to GPR
|
||||
# run: |
|
||||
# mkdir -p $HOME/.gem
|
||||
# touch $HOME/.gem/credentials
|
||||
# chmod 0600 $HOME/.gem/credentials
|
||||
# printf -- "---\n:github: Bearer ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
||||
# gem build *.gemspec
|
||||
# gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
|
||||
# env:
|
||||
# GEM_HOST_API_KEY: ${{secrets.GITHUB_TOKEN}}
|
||||
# OWNER: wpscanteam
|
||||
|
||||
- name: Publish to RubyGems
|
||||
run: |
|
||||
mkdir -p $HOME/.gem
|
||||
touch $HOME/.gem/credentials
|
||||
chmod 0600 $HOME/.gem/credentials
|
||||
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
||||
gem build *.gemspec
|
||||
gem push *.gem
|
||||
env:
|
||||
GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
|
||||
34
.github/workflows/ruby.yml
vendored
Normal file
34
.github/workflows/ruby.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Ruby
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up Ruby 2.6
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.6.x
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/wpscan.gemspec') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gem-
|
||||
- name: Build and test
|
||||
run: |
|
||||
gem install bundler
|
||||
bundle config force_ruby_platform true
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
- name: test
|
||||
run: |
|
||||
bundle exec rspec
|
||||
- name: rubocop
|
||||
run: |
|
||||
bundle exec rubocop
|
||||
5
.rspec
5
.rspec
@@ -1,3 +1,2 @@
|
||||
--color
|
||||
--fail-fast
|
||||
--require spec_helper
|
||||
--require spec_helper
|
||||
--color
|
||||
17
.rubocop.yml
17
.rubocop.yml
@@ -4,12 +4,8 @@ AllCops:
|
||||
Exclude:
|
||||
- '*.gemspec'
|
||||
- 'vendor/**/*'
|
||||
ClassVars:
|
||||
Enabled: false
|
||||
LineLength:
|
||||
Layout/LineLength:
|
||||
Max: 120
|
||||
MethodLength:
|
||||
Max: 20
|
||||
Lint/UriEscapeUnescape:
|
||||
Enabled: false
|
||||
Metrics/AbcSize:
|
||||
@@ -19,9 +15,20 @@ Metrics/BlockLength:
|
||||
- 'spec/**/*'
|
||||
Metrics/ClassLength:
|
||||
Max: 150
|
||||
Exclude:
|
||||
- 'app/controllers/enumeration/cli_options.rb'
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 8
|
||||
Metrics/MethodLength:
|
||||
Max: 20
|
||||
Exclude:
|
||||
- 'app/controllers/enumeration/cli_options.rb'
|
||||
Style/ClassVars:
|
||||
Enabled: false
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
Style/FormatStringToken:
|
||||
Enabled: false
|
||||
Style/NumericPredicate:
|
||||
Exclude:
|
||||
- 'app/controllers/vuln_api.rb'
|
||||
|
||||
26
.travis.yml
26
.travis.yml
@@ -2,29 +2,13 @@ language: ruby
|
||||
sudo: false
|
||||
cache: bundler
|
||||
rvm:
|
||||
- 2.4.1
|
||||
- 2.4.2
|
||||
- 2.4.3
|
||||
- 2.4.4
|
||||
- 2.4.5
|
||||
- 2.4.6
|
||||
- 2.5.0
|
||||
- 2.5.1
|
||||
- 2.5.2
|
||||
- 2.5.3
|
||||
- 2.5.4
|
||||
- 2.5.5
|
||||
- 2.6.0
|
||||
- 2.6.1
|
||||
- 2.6.2
|
||||
- 2.6.3
|
||||
- ruby-head
|
||||
before_install:
|
||||
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||
- gem update --system
|
||||
- 2.4.9
|
||||
- 2.5.7
|
||||
- 2.6.5
|
||||
- 2.7.0
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: ruby-head
|
||||
- rvm: 2.7.0
|
||||
script:
|
||||
- bundle exec rubocop
|
||||
- bundle exec rspec
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ruby:2.6.2-alpine3.9 AS builder
|
||||
FROM ruby:2.6.3-alpine AS builder
|
||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||
|
||||
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
||||
@@ -19,7 +19,7 @@ RUN rake install --trace
|
||||
RUN chmod -R a+r /usr/local/bundle
|
||||
|
||||
|
||||
FROM ruby:2.6.2-alpine3.9
|
||||
FROM ruby:2.6.3-alpine
|
||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||
|
||||
RUN adduser -h /wpscan -g WPScan -D wpscan
|
||||
@@ -38,4 +38,3 @@ USER wpscan
|
||||
RUN /usr/local/bundle/bin/wpscan --update --verbose
|
||||
|
||||
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]
|
||||
CMD ["--help"]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -29,8 +29,6 @@ Example cases which do not require a commercial license, and thus fall under the
|
||||
|
||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
||||
|
||||
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
||||
|
||||
Free-use Terms and Conditions;
|
||||
|
||||
3. Redistribution
|
||||
|
||||
46
README.md
46
README.md
@@ -17,7 +17,6 @@
|
||||
<a href="https://badge.fury.io/rb/wpscan" target="_blank"><img src="https://badge.fury.io/rb/wpscan.svg"></a>
|
||||
<a href="https://travis-ci.org/wpscanteam/wpscan" target="_blank"><img src="https://travis-ci.org/wpscanteam/wpscan.svg?branch=master"></a>
|
||||
<a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
|
||||
<a href="https://www.patreon.com/wpscan" target="_blank"><img src="https://img.shields.io/badge/patreon-donate-green.svg"></a>
|
||||
</p>
|
||||
|
||||
# INSTALL
|
||||
@@ -78,41 +77,60 @@ docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u1-1
|
||||
|
||||
# Usage
|
||||
|
||||
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings. If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
||||
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings.
|
||||
|
||||
If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
||||
As a result, when using the ```--enumerate``` option, don't forget to set the ```--plugins-detection``` accordingly, as its default is 'passive'.
|
||||
|
||||
For more options, open a terminal and type ```wpscan --help``` (if you built wpscan from the source, you should type the command outside of the git repo)
|
||||
|
||||
The DB is located at ~/.wpscan/db
|
||||
|
||||
## Vulnerability Database
|
||||
|
||||
The WPScan CLI tool uses the [WPVulnDB API](https://wpvulndb.com/api) to retrieve WordPress vulnerability data in real time. For WPScan to retrieve the vulnerability data an API token must be supplied via the `--api-token` option, or via a configuration file, as discussed below. An API token can be obtained by registering an account on [WPVulnDB](https://wpvulndb.com/users/sign_up). Up to 50 API requests per day are given free of charge to registered users. Once the 50 API requests are exhausted, WPScan will continue to work as normal but without any vulnerability data. Users can upgrade to paid API usage to increase their API limits within their user profile on [WPVulnDB](https://wpvulndb.com/).
|
||||
|
||||
## Load CLI options from file/s
|
||||
|
||||
WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
|
||||
|
||||
- ~/.wpscan/cli_options.json
|
||||
- ~/.wpscan/cli_options.yml
|
||||
- pwd/.wpscan/cli_options.json
|
||||
- pwd/.wpscan/cli_options.yml
|
||||
- ~/.wpscan/scan.json
|
||||
- ~/.wpscan/scan.yml
|
||||
- pwd/.wpscan/scan.json
|
||||
- pwd/.wpscan/scan.yml
|
||||
|
||||
If those files exist, options from them will be loaded and overridden if found twice.
|
||||
If those files exist, options from the `cli_options` key will be loaded and overridden if found twice.
|
||||
|
||||
e.g:
|
||||
|
||||
~/.wpscan/cli_options.yml:
|
||||
~/.wpscan/scan.yml:
|
||||
|
||||
```yml
|
||||
proxy: 'http://127.0.0.1:8080'
|
||||
verbose: true
|
||||
cli_options:
|
||||
proxy: 'http://127.0.0.1:8080'
|
||||
verbose: true
|
||||
```
|
||||
|
||||
pwd/.wpscan/cli_options.yml:
|
||||
pwd/.wpscan/scan.yml:
|
||||
|
||||
```yml
|
||||
proxy: 'socks5://127.0.0.1:9090'
|
||||
url: 'http://target.tld'
|
||||
cli_options:
|
||||
proxy: 'socks5://127.0.0.1:9090'
|
||||
url: 'http://target.tld'
|
||||
```
|
||||
|
||||
Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
|
||||
|
||||
Enumerating usernames
|
||||
## Save API Token in a file
|
||||
|
||||
The feature mentioned above is useful to keep the API Token in a config file and not have to supply it via the CLI each time. To do so, create the ~/.wpscan/scan.yml file containing the below:
|
||||
|
||||
```yml
|
||||
cli_options:
|
||||
api_token: YOUR_API_TOKEN
|
||||
```
|
||||
|
||||
## Enumerating usernames
|
||||
|
||||
```shell
|
||||
wpscan --url https://target.tld/ --enumerate u
|
||||
|
||||
6
Rakefile
6
Rakefile
@@ -6,14 +6,18 @@ exec = []
|
||||
|
||||
begin
|
||||
require 'rubocop/rake_task'
|
||||
|
||||
RuboCop::RakeTask.new
|
||||
|
||||
exec << :rubocop
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
begin
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec) { |t| t.rspec_opts = %w{--tag ~slow} }
|
||||
|
||||
exec << :spec
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'controllers/core'
|
||||
require_relative 'controllers/vuln_api'
|
||||
require_relative 'controllers/custom_directories'
|
||||
require_relative 'controllers/wp_version'
|
||||
require_relative 'controllers/main_theme'
|
||||
|
||||
@@ -18,7 +18,7 @@ module WPScan
|
||||
target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir
|
||||
target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir
|
||||
|
||||
return if target.content_dir(ParsedCli.detection_mode)
|
||||
return if target.content_dir
|
||||
|
||||
raise Error::WpContentDirNotDetected
|
||||
end
|
||||
|
||||
@@ -7,15 +7,6 @@ module WPScan
|
||||
module Controller
|
||||
# Enumeration Controller
|
||||
class Enumeration < CMSScanner::Controller::Base
|
||||
def before_scan
|
||||
DB::DynamicFinders::Plugin.create_versions_finders
|
||||
DB::DynamicFinders::Theme.create_versions_finders
|
||||
|
||||
# Force the Garbage Collector to run due to the above method being
|
||||
# quite heavy in objects allocation
|
||||
GC.start
|
||||
end
|
||||
|
||||
def run
|
||||
enum = ParsedCli.enumerate || {}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ module WPScan
|
||||
end
|
||||
|
||||
# @return [ Array<OptParseValidator::OptBase> ]
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def cli_enum_choices
|
||||
[
|
||||
OptMultiChoices.new(
|
||||
@@ -19,10 +18,10 @@ module WPScan
|
||||
choices: {
|
||||
vp: OptBoolean.new(['--vulnerable-plugins']),
|
||||
ap: OptBoolean.new(['--all-plugins']),
|
||||
p: OptBoolean.new(['--plugins']),
|
||||
p: OptBoolean.new(['--popular-plugins']),
|
||||
vt: OptBoolean.new(['--vulnerable-themes']),
|
||||
at: OptBoolean.new(['--all-themes']),
|
||||
t: OptBoolean.new(['--themes']),
|
||||
t: OptBoolean.new(['--popular-themes']),
|
||||
tt: OptBoolean.new(['--timthumbs']),
|
||||
cb: OptBoolean.new(['--config-backups']),
|
||||
dbe: OptBoolean.new(['--db-exports']),
|
||||
@@ -45,7 +44,6 @@ module WPScan
|
||||
)
|
||||
]
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# @return [ Array<OptParseValidator::OptBase> ]
|
||||
def cli_plugins_opts
|
||||
@@ -67,6 +65,11 @@ module WPScan
|
||||
'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
|
||||
'or --plugins-detection modes.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
|
||||
),
|
||||
OptInteger.new(
|
||||
['--plugins-threshold THRESHOLD',
|
||||
'Raise an error when the number of detected plugins via known locations reaches the threshold. ' \
|
||||
'Set to 0 to ignore the threshold.'], default: 100, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
@@ -91,6 +94,11 @@ module WPScan
|
||||
'Use the supplied mode to check themes versions instead of the --detection-mode ' \
|
||||
'or --themes-detection modes.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||
),
|
||||
OptInteger.new(
|
||||
['--themes-threshold THRESHOLD',
|
||||
'Raise an error when the number of detected themes via known locations reaches the threshold. ' \
|
||||
'Set to 0 to ignore the threshold.'], default: 20, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
@@ -56,12 +56,13 @@ module WPScan
|
||||
#
|
||||
# @return [ Boolean ] Wether or not to enumerate the plugins
|
||||
def enum_plugins?(opts)
|
||||
opts[:plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
||||
opts[:popular_plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
||||
end
|
||||
|
||||
def enum_plugins
|
||||
opts = default_opts('plugins').merge(
|
||||
list: plugins_list_from_opts(ParsedCli.options),
|
||||
threshold: ParsedCli.plugins_threshold,
|
||||
sort: true
|
||||
)
|
||||
|
||||
@@ -91,7 +92,7 @@ module WPScan
|
||||
|
||||
if opts[:enumerate][:all_plugins]
|
||||
DB::Plugins.all_slugs
|
||||
elsif opts[:enumerate][:plugins]
|
||||
elsif opts[:enumerate][:popular_plugins]
|
||||
DB::Plugins.popular_slugs
|
||||
else
|
||||
DB::Plugins.vulnerable_slugs
|
||||
@@ -102,12 +103,13 @@ module WPScan
|
||||
#
|
||||
# @return [ Boolean ] Wether or not to enumerate the themes
|
||||
def enum_themes?(opts)
|
||||
opts[:themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
||||
opts[:popular_themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
||||
end
|
||||
|
||||
def enum_themes
|
||||
opts = default_opts('themes').merge(
|
||||
list: themes_list_from_opts(ParsedCli.options),
|
||||
threshold: ParsedCli.themes_threshold,
|
||||
sort: true
|
||||
)
|
||||
|
||||
@@ -137,7 +139,7 @@ module WPScan
|
||||
|
||||
if opts[:enumerate][:all_themes]
|
||||
DB::Themes.all_slugs
|
||||
elsif opts[:enumerate][:themes]
|
||||
elsif opts[:enumerate][:popular_themes]
|
||||
DB::Themes.popular_slugs
|
||||
else
|
||||
DB::Themes.vulnerable_slugs
|
||||
|
||||
@@ -65,30 +65,43 @@ module WPScan
|
||||
|
||||
case ParsedCli.password_attack
|
||||
when :wp_login
|
||||
WPScan::Finders::Passwords::WpLogin.new(target)
|
||||
Finders::Passwords::WpLogin.new(target)
|
||||
when :xmlrpc
|
||||
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||
|
||||
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||
Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||
when :xmlrpc_multicall
|
||||
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
|
||||
|
||||
# @return [ CMSScanner::Finders::Finder ]
|
||||
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
|
||||
|
||||
if wp_version && wp_version < '4.4'
|
||||
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||
Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||
else
|
||||
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||
Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||
end
|
||||
else
|
||||
WPScan::Finders::Passwords::WpLogin.new(target)
|
||||
Finders::Passwords::WpLogin.new(target)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
30
app/controllers/vuln_api.rb
Normal file
30
app/controllers/vuln_api.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Controller to handle the API token
|
||||
class VulnApi < CMSScanner::Controller::Base
|
||||
def cli_options
|
||||
[
|
||||
OptString.new(['--api-token TOKEN', 'The WPVulnDB API Token to display vulnerability data'])
|
||||
]
|
||||
end
|
||||
|
||||
def before_scan
|
||||
return unless ParsedCli.api_token
|
||||
|
||||
DB::VulnApi.token = ParsedCli.api_token
|
||||
|
||||
api_status = DB::VulnApi.status
|
||||
|
||||
raise Error::InvalidApiToken if api_status['error']
|
||||
raise Error::ApiLimitReached if api_status['requests_remaining'] == 0
|
||||
raise api_status['http_error'] if api_status['http_error']
|
||||
end
|
||||
|
||||
def after_scan
|
||||
output('status', status: DB::VulnApi.status, api_requests: WPScan.api_requests)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -17,7 +17,7 @@ module WPScan
|
||||
end
|
||||
|
||||
def before_scan
|
||||
WPScan::DB::DynamicFinders::Wordpress.create_versions_finders
|
||||
DB::DynamicFinders::Wordpress.create_versions_finders
|
||||
end
|
||||
|
||||
def run
|
||||
|
||||
@@ -4,7 +4,6 @@ module WPScan
|
||||
module Finders
|
||||
module DbExports
|
||||
# DB Exports finder
|
||||
# See https://github.com/wpscanteam/wpscan-v3/issues/62
|
||||
class KnownLocations < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Enumerator
|
||||
|
||||
@@ -20,9 +19,9 @@ module WPScan
|
||||
|
||||
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
||||
if res.effective_url.end_with?('.zip')
|
||||
next unless res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
||||
next unless %r{\Aapplication/zip}i.match?(res.headers['Content-Type'])
|
||||
else
|
||||
next unless res.body =~ SQL_PATTERN
|
||||
next unless SQL_PATTERN.match?(res.body)
|
||||
end
|
||||
|
||||
found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
||||
@@ -41,7 +40,7 @@ module WPScan
|
||||
# @return [ Hash ]
|
||||
def potential_urls(opts = {})
|
||||
urls = {}
|
||||
domain_name = target.uri.host[/(^[\w|-]+)/, 1]
|
||||
domain_name = PublicSuffix.domain(target.uri.host)[/(^[\w|-]+)/, 1]
|
||||
|
||||
File.open(opts[:list]).each_with_index do |path, index|
|
||||
path.gsub!('{domain_name}', domain_name)
|
||||
|
||||
@@ -9,7 +9,7 @@ module WPScan
|
||||
def aggressive(_opts = {})
|
||||
path = 'installer-log.txt'
|
||||
|
||||
return unless target.head_and_get(path).body =~ /DUPLICATOR INSTALL-LOG/
|
||||
return unless /DUPLICATOR INSTALL-LOG/.match?(target.head_and_get(path).body)
|
||||
|
||||
Model::DuplicatorInstallerLog.new(
|
||||
target.url(path),
|
||||
|
||||
@@ -10,7 +10,7 @@ module WPScan
|
||||
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
||||
|
||||
target.in_scope_uris(target.homepage_res) do |uri|
|
||||
next unless uri.path =~ pattern
|
||||
next unless uri.path&.match?(pattern)
|
||||
|
||||
url = target.url('wp-content/mu-plugins/')
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ module WPScan
|
||||
path = 'wp-content/uploads/dump.sql'
|
||||
res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } })
|
||||
|
||||
return unless res.body =~ SQL_PATTERN
|
||||
return unless SQL_PATTERN.match?(res.body)
|
||||
|
||||
Model::UploadSQLDump.new(
|
||||
target.url(path),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'main_theme/css_style'
|
||||
require_relative 'main_theme/css_style_in_homepage'
|
||||
require_relative 'main_theme/css_style_in_404_page'
|
||||
require_relative 'main_theme/woo_framework_meta_generator'
|
||||
require_relative 'main_theme/urls_in_homepage'
|
||||
require_relative 'main_theme/urls_in_404_page'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
@@ -14,9 +16,11 @@ module WPScan
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders <<
|
||||
MainTheme::CssStyle.new(target) <<
|
||||
MainTheme::CssStyleInHomepage.new(target) <<
|
||||
MainTheme::CssStyleIn404Page.new(target) <<
|
||||
MainTheme::WooFrameworkMetaGenerator.new(target) <<
|
||||
MainTheme::UrlsInHomepage.new(target)
|
||||
MainTheme::UrlsInHomepage.new(target) <<
|
||||
MainTheme::UrlsIn404Page.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
14
app/finders/main_theme/css_style_in_404_page.rb
Normal file
14
app/finders/main_theme/css_style_in_404_page.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# From the CSS style in the 404 page
|
||||
class CssStyleIn404Page < CssStyleInHomepage
|
||||
def passive(opts = {})
|
||||
passive_from_css_href(target.error_404_res, opts) || passive_from_style_code(target.error_404_res, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,9 +3,9 @@
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# From the css style
|
||||
class CssStyle < CMSScanner::Finders::Finder
|
||||
include Finders::WpItems::URLsInHomepage
|
||||
# From the CSS style in the homepage
|
||||
class CssStyleInHomepage < CMSScanner::Finders::Finder
|
||||
include Finders::WpItems::UrlsInPage # To have the item_code_pattern method available here
|
||||
|
||||
def create_theme(slug, style_url, opts)
|
||||
Model::Theme.new(
|
||||
15
app/finders/main_theme/urls_in_404_page.rb
Normal file
15
app/finders/main_theme/urls_in_404_page.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# URLs In 404 Page Finder
|
||||
class UrlsIn404Page < UrlsInHomepage
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.error_404_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ module WPScan
|
||||
module MainTheme
|
||||
# URLs In Homepage Finder
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
include WpItems::UrlsInPage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
@@ -21,6 +21,11 @@ module WPScan
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.homepage_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ module WPScan
|
||||
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze
|
||||
|
||||
def passive(opts = {})
|
||||
return unless target.homepage_res.body =~ PATTERN
|
||||
return unless target.homepage_res.body =~ PATTERN || target.error_404_res.body =~ PATTERN
|
||||
|
||||
Model::Theme.new(
|
||||
Regexp.last_match[1],
|
||||
|
||||
@@ -13,7 +13,7 @@ module WPScan
|
||||
|
||||
def valid_credentials?(response)
|
||||
response.code == 302 &&
|
||||
response.headers['Set-Cookie']&.any? { |cookie| cookie =~ /wordpress_logged_in_/i }
|
||||
[*response.headers['Set-Cookie']]&.any? { |cookie| cookie =~ /wordpress_logged_in_/i }
|
||||
end
|
||||
|
||||
def errored_response?(response)
|
||||
|
||||
@@ -8,7 +8,7 @@ module WPScan
|
||||
include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack
|
||||
|
||||
def login_request(username, password)
|
||||
target.method_call('wp.getUsersBlogs', [username, password])
|
||||
target.method_call('wp.getUsersBlogs', [username, password], cache_ttl: 0)
|
||||
end
|
||||
|
||||
def valid_credentials?(response)
|
||||
|
||||
@@ -19,7 +19,7 @@ module WPScan
|
||||
end
|
||||
end
|
||||
|
||||
target.multi_call(methods).run
|
||||
target.multi_call(methods, cache_ttl: 0).run
|
||||
end
|
||||
|
||||
# @param [ Array<Model::User> ] users
|
||||
|
||||
@@ -13,25 +13,15 @@ module WPScan
|
||||
def initialize(plugin)
|
||||
finders << PluginVersion::Readme.new(plugin)
|
||||
|
||||
load_specific_finders(plugin)
|
||||
create_and_load_dynamic_versions_finders(plugin)
|
||||
end
|
||||
|
||||
# Load the finders associated with the plugin
|
||||
# Create the dynamic version finders related to the plugin and register them
|
||||
#
|
||||
# @param [ Model::Plugin ] plugin
|
||||
def load_specific_finders(plugin)
|
||||
module_name = plugin.classify
|
||||
|
||||
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)
|
||||
def create_and_load_dynamic_versions_finders(plugin)
|
||||
DB::DynamicFinders::Plugin.create_versions_finders(plugin.slug).each do |finder|
|
||||
finders << finder.new(plugin)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ module WPScan
|
||||
|
||||
# The target(plugin)#readme_url can't be used directly here
|
||||
# as if the --detection-mode is passive, it will always return nil
|
||||
Model::WpItem::READMES.each do |file|
|
||||
target.potential_readme_filenames.each do |file|
|
||||
res = target.head_and_get(file)
|
||||
|
||||
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
|
||||
@@ -52,7 +52,7 @@ module WPScan
|
||||
|
||||
number = Regexp.last_match[1]
|
||||
|
||||
number if number =~ /[0-9]+/
|
||||
number if /[0-9]+/.match?(number)
|
||||
end
|
||||
|
||||
# @param [ String ] body
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'plugins/urls_in_homepage'
|
||||
require_relative 'plugins/urls_in_404_page'
|
||||
require_relative 'plugins/known_locations'
|
||||
# From the DynamicFinders
|
||||
require_relative 'plugins/comment'
|
||||
@@ -22,6 +23,7 @@ module WPScan
|
||||
def initialize(target)
|
||||
finders <<
|
||||
Plugins::UrlsInHomepage.new(target) <<
|
||||
Plugins::UrlsIn404Page.new(target) <<
|
||||
Plugins::HeaderPattern.new(target) <<
|
||||
Plugins::Comment.new(target) <<
|
||||
Plugins::Xpath.new(target) <<
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from Dynamic Finder 'BodyPattern'
|
||||
class BodyPattern < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
class BodyPattern < Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 30
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
@@ -15,7 +15,7 @@ module WPScan
|
||||
#
|
||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||
def process_response(opts, response, slug, klass, config)
|
||||
return unless response.body =~ config['pattern']
|
||||
return unless response.body&.match?(config['pattern'])
|
||||
|
||||
Model::Plugin.new(
|
||||
slug,
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from the Dynamic Finder 'Comment'
|
||||
class Comment < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
class Comment < Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 30
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
@@ -18,7 +18,7 @@ module WPScan
|
||||
response.html.xpath(config['xpath'] || '//comment()').each do |node|
|
||||
comment = node.text.to_s.strip
|
||||
|
||||
next unless comment =~ config['pattern']
|
||||
next unless comment&.match?(config['pattern'])
|
||||
|
||||
return Model::Plugin.new(
|
||||
slug,
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from Dynamic Finder 'ConfigParser'
|
||||
class ConfigParser < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
class ConfigParser < Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 40
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from Dynamic Finder 'HeaderPattern'
|
||||
class HeaderPattern < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
class HeaderPattern < Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 30
|
||||
|
||||
# @param [ Hash ] opts
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from the Dynamic Finder 'JavascriptVar'
|
||||
class JavascriptVar < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
class JavascriptVar < Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 60
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
|
||||
@@ -19,8 +19,14 @@ module WPScan
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, slug|
|
||||
finding_opts = opts.merge(found_by: found_by,
|
||||
confidence: 80,
|
||||
interesting_entries: ["#{res.effective_url}, status: #{res.code}"])
|
||||
|
||||
found << Model::Plugin.new(slug, target, finding_opts)
|
||||
|
||||
raise Error::PluginsThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
||||
end
|
||||
|
||||
found
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from Dynamic Finder 'QueryParameter'
|
||||
class QueryParameter < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
class QueryParameter < Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 10
|
||||
|
||||
def passive(_opts = {})
|
||||
|
||||
16
app/finders/plugins/urls_in_404_page.rb
Normal file
16
app/finders/plugins/urls_in_404_page.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# URLs In 404 Page Finder
|
||||
# Typically, the items detected from URLs like /wp-content/plugins/<slug>/
|
||||
class UrlsIn404Page < UrlsInHomepage
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.error_404_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,10 +4,9 @@ module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# URLs In Homepage Finder
|
||||
# Typically, the items detected from URLs like
|
||||
# /wp-content/plugins/<slug>/
|
||||
# Typically, the items detected from URLs like /wp-content/plugins/<slug>/
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
include WpItems::UrlsInPage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
@@ -21,6 +20,11 @@ module WPScan
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.homepage_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from the Dynamic Finder 'Xpath'
|
||||
class Xpath < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
class Xpath < Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 40
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
|
||||
@@ -16,25 +16,15 @@ module WPScan
|
||||
ThemeVersion::Style.new(theme) <<
|
||||
ThemeVersion::WooFrameworkMetaGenerator.new(theme)
|
||||
|
||||
load_specific_finders(theme)
|
||||
create_and_load_dynamic_versions_finders(theme)
|
||||
end
|
||||
|
||||
# Load the finders associated with the theme
|
||||
# Create the dynamic version finders related to the theme and register them
|
||||
#
|
||||
# @param [ Model::Theme ] theme
|
||||
def load_specific_finders(theme)
|
||||
module_name = theme.classify
|
||||
|
||||
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)
|
||||
def create_and_load_dynamic_versions_finders(theme)
|
||||
DB::DynamicFinders::Theme.create_versions_finders(theme.slug).each do |finder|
|
||||
finders << finder.new(theme)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'themes/urls_in_homepage'
|
||||
require_relative 'themes/urls_in_404_page'
|
||||
require_relative 'themes/known_locations'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Themes
|
||||
# themes Finder
|
||||
# Themes Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::SameTypeFinder
|
||||
|
||||
@@ -14,6 +15,7 @@ module WPScan
|
||||
def initialize(target)
|
||||
finders <<
|
||||
Themes::UrlsInHomepage.new(target) <<
|
||||
Themes::UrlsIn404Page.new(target) <<
|
||||
Themes::KnownLocations.new(target)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,8 +19,14 @@ module WPScan
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, slug|
|
||||
finding_opts = opts.merge(found_by: found_by,
|
||||
confidence: 80,
|
||||
interesting_entries: ["#{res.effective_url}, status: #{res.code}"])
|
||||
|
||||
found << Model::Theme.new(slug, target, finding_opts)
|
||||
|
||||
raise Error::ThemesThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
||||
end
|
||||
|
||||
found
|
||||
|
||||
15
app/finders/themes/urls_in_404_page.rb
Normal file
15
app/finders/themes/urls_in_404_page.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Themes
|
||||
# URLs In 04 Page Finder
|
||||
class UrlsIn404Page < UrlsInHomepage
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.error_404_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ module WPScan
|
||||
module Themes
|
||||
# URLs In Homepage Finder
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
include WpItems::UrlsInPage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
@@ -19,6 +19,11 @@ module WPScan
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.homepage_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,7 +22,7 @@ module WPScan
|
||||
found = []
|
||||
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res|
|
||||
next unless res.body =~ /no image specified/i
|
||||
next unless /no image specified/i.match?(res.body)
|
||||
|
||||
found << Model::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
|
||||
end
|
||||
|
||||
@@ -97,9 +97,12 @@ module WPScan
|
||||
# @return [ String, nil ]
|
||||
def display_name_from_body(body)
|
||||
page = Nokogiri::HTML.parse(body)
|
||||
|
||||
# WP >= 3.0
|
||||
page.css('h1.page-title span').each do |node|
|
||||
return node.text.to_s
|
||||
text = node.text.to_s.strip
|
||||
|
||||
return text unless text.empty?
|
||||
end
|
||||
|
||||
# WP < 3.0
|
||||
|
||||
@@ -24,7 +24,7 @@ module WPScan
|
||||
|
||||
return found if error.empty? # Protection plugin / error disabled
|
||||
|
||||
next unless error =~ /The password you entered for the username|Incorrect Password/i
|
||||
next unless /The password you entered for the username|Incorrect Password/i.match?(error)
|
||||
|
||||
found << Model::User.new(username, found_by: found_by, confidence: 100)
|
||||
end
|
||||
|
||||
@@ -34,6 +34,8 @@ module WPScan
|
||||
def user_details_from_oembed_data(oembed_data)
|
||||
return unless oembed_data
|
||||
|
||||
oembed_data = oembed_data.first if oembed_data.is_a?(Array)
|
||||
|
||||
if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z}
|
||||
details = [Regexp.last_match[1], 'Author URL', 90]
|
||||
elsif oembed_data['author_name'] && !oembed_data['author_name'].empty?
|
||||
|
||||
@@ -6,7 +6,7 @@ module WPScan
|
||||
# Users disclosed from the dc:creator field in the RSS
|
||||
# The names disclosed are display names, however depending on the configuration of the blog,
|
||||
# they can be the same than usernames
|
||||
class RSSGenerator < WPScan::Finders::WpVersion::RSSGenerator
|
||||
class RSSGenerator < Finders::WpVersion::RSSGenerator
|
||||
def process_urls(urls, _opts = {})
|
||||
found = []
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'wp_items/urls_in_homepage'
|
||||
require_relative 'wp_items/urls_in_page'
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module Finders
|
||||
module WpItems
|
||||
# URLs In Homepage Module to use in plugins & themes finders
|
||||
module URLsInHomepage
|
||||
module UrlsInPage
|
||||
# @param [ String ] type plugins / themes
|
||||
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
|
||||
#
|
||||
@@ -12,10 +12,12 @@ module WPScan
|
||||
def items_from_links(type, uniq = true)
|
||||
found = []
|
||||
|
||||
target.in_scope_uris(target.homepage_res) do |uri|
|
||||
target.in_scope_uris(page_res) do |uri|
|
||||
next unless uri.to_s =~ item_attribute_pattern(type)
|
||||
|
||||
found << Regexp.last_match[1]
|
||||
slug = Regexp.last_match[1]&.strip
|
||||
|
||||
found << slug unless slug&.empty?
|
||||
end
|
||||
|
||||
uniq ? found.uniq.sort : found.sort
|
||||
@@ -28,7 +30,7 @@ module WPScan
|
||||
def items_from_codes(type, uniq = true)
|
||||
found = []
|
||||
|
||||
target.homepage_res.html.css('script,style').each do |tag|
|
||||
page_res.html.xpath('//script[not(@src)]|//style[not(@src)]').each do |tag|
|
||||
code = tag.text.to_s
|
||||
next if code.empty?
|
||||
|
||||
@@ -42,7 +44,7 @@ module WPScan
|
||||
#
|
||||
# @return [ Regexp ]
|
||||
def item_attribute_pattern(type)
|
||||
@item_attribute_pattern ||= %r{\A#{item_url_pattern(type)}([^/]+)/}i
|
||||
@item_attribute_pattern ||= %r{#{item_url_pattern(type)}([^/]+)/}i
|
||||
end
|
||||
|
||||
# @param [ String ] type
|
||||
@@ -59,7 +61,7 @@ module WPScan
|
||||
item_dir = type == 'plugins' ? target.plugins_dir : target.content_dir
|
||||
item_url = type == 'plugins' ? target.plugins_url : target.content_url
|
||||
|
||||
url = /#{item_url.gsub(/\A(?:http|https)/i, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
url = /#{item_url.gsub(/\A(?:https?)/i, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
item_dir = %r{(?:#{url}|\\?\/#{item_dir.gsub('/', '\\\\\?\/')}\\?/)}i
|
||||
|
||||
type == 'plugins' ? item_dir : %r{#{item_dir}#{type}\\?\/}i
|
||||
@@ -28,7 +28,7 @@ module WPScan
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
(%w[RSSGenerator AtomGenerator RDFGenerator] +
|
||||
WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
|
||||
DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
|
||||
%w[Readme UniqueFingerprinting]
|
||||
).each do |finder_name|
|
||||
finders << WpVersion.const_get(finder_name.to_sym).new(target)
|
||||
|
||||
@@ -15,9 +15,16 @@ module WPScan
|
||||
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
||||
end
|
||||
|
||||
# @return [ JSON ]
|
||||
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||
# or the local metadata db otherwise
|
||||
# @return [ Hash ]
|
||||
def metadata
|
||||
@metadata ||= db_data.empty? ? DB::Plugin.metadata_at(slug) : db_data
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def db_data
|
||||
@db_data ||= DB::Plugin.db_data(slug)
|
||||
@db_data ||= DB::VulnApi.plugin_data(slug)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
@@ -28,6 +35,11 @@ module WPScan
|
||||
|
||||
@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
|
||||
|
||||
@@ -21,9 +21,16 @@ module WPScan
|
||||
parse_style
|
||||
end
|
||||
|
||||
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||
# or the local metadata db otherwise
|
||||
# @return [ JSON ]
|
||||
def metadata
|
||||
@metadata ||= db_data.empty? ? DB::Theme.metadata_at(slug) : db_data
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def db_data
|
||||
@db_data ||= DB::Theme.db_data(slug)
|
||||
@db_data ||= DB::VulnApi.theme_data(slug)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
@@ -94,7 +101,7 @@ module WPScan
|
||||
#
|
||||
# @return [ String ]
|
||||
def parse_style_tag(body, tag)
|
||||
value = body[/^\s*#{Regexp.escape(tag)}:[\t ]*([^\r\n]+)/i, 1]
|
||||
value = body[/#{Regexp.escape(tag)}:[\t ]*([^\r\n\*]+)/i, 1]
|
||||
|
||||
value && !value.strip.empty? ? value.strip : nil
|
||||
end
|
||||
|
||||
@@ -9,11 +9,12 @@ module WPScan
|
||||
include CMSScanner::Target::Platform::PHP
|
||||
include CMSScanner::Target::Server::Generic
|
||||
|
||||
# Most common readme filenames, based on checking all public plugins and themes.
|
||||
READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
|
||||
|
||||
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
||||
|
||||
delegate :homepage_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
|
||||
delegate :homepage_res, :error_404_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
|
||||
|
||||
# @param [ String ] slug The plugin/theme slug
|
||||
# @param [ Target ] blog The targeted blog
|
||||
@@ -22,7 +23,7 @@ module WPScan
|
||||
# @option opts [ Hash ] :version_detection The options to use when looking for the version
|
||||
# @option opts [ String ] :url The URL of the item
|
||||
def initialize(slug, blog, opts = {})
|
||||
@slug = URI.decode(slug)
|
||||
@slug = Addressable::URI.unencode(slug)
|
||||
@blog = blog
|
||||
@uri = Addressable::URI.parse(opts[:url]) if opts[:url]
|
||||
|
||||
@@ -59,18 +60,18 @@ module WPScan
|
||||
|
||||
# @return [ String ]
|
||||
def latest_version
|
||||
@latest_version ||= db_data['latest_version'] ? Model::Version.new(db_data['latest_version']) : nil
|
||||
@latest_version ||= metadata['latest_version'] ? Model::Version.new(metadata['latest_version']) : nil
|
||||
end
|
||||
|
||||
# Not used anywhere ATM
|
||||
# @return [ Boolean ]
|
||||
def popular?
|
||||
@popular ||= db_data['popular']
|
||||
@popular ||= metadata['popular'] ? true : false
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def last_updated
|
||||
@last_updated ||= db_data['last_updated']
|
||||
@last_updated ||= metadata['last_updated']
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
@@ -82,11 +83,6 @@ module WPScan
|
||||
end
|
||||
end
|
||||
|
||||
# URI.encode is preferered over Addressable::URI.encode as it will encode
|
||||
# leading # character:
|
||||
# URI.encode('#t#') => %23t%23
|
||||
# Addressable::URI.encode('#t#') => #t%23
|
||||
#
|
||||
# @param [ String ] path Optional path to merge with the uri
|
||||
#
|
||||
# @return [ String ]
|
||||
@@ -94,7 +90,7 @@ module WPScan
|
||||
return unless @uri
|
||||
return @uri.to_s unless path
|
||||
|
||||
@uri.join(URI.encode(path)).to_s
|
||||
@uri.join(Addressable::URI.encode(path)).to_s
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
@@ -117,7 +113,7 @@ module WPScan
|
||||
|
||||
return @readme_url unless @readme_url.nil?
|
||||
|
||||
READMES.each do |path|
|
||||
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
|
||||
@@ -126,6 +122,10 @@ module WPScan
|
||||
@readme_url = false
|
||||
end
|
||||
|
||||
def potential_readme_filenames
|
||||
@potential_readme_filenames ||= READMES
|
||||
end
|
||||
|
||||
# @param [ String ] path
|
||||
# @param [ Hash ] params The request params
|
||||
#
|
||||
@@ -161,7 +161,7 @@ module WPScan
|
||||
# @return [ Typhoeus::Response ]
|
||||
def head_and_get(path, codes = [200], params = {})
|
||||
final_path = +@path_from_blog
|
||||
final_path << URI.encode(path) unless path.nil?
|
||||
final_path << path unless path.nil?
|
||||
|
||||
blog.head_and_get(final_path, codes, params)
|
||||
end
|
||||
|
||||
@@ -35,9 +35,16 @@ module WPScan
|
||||
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
||||
end
|
||||
|
||||
# @return [ JSON ]
|
||||
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||
# or the local metadata db otherwise
|
||||
# @return [ Hash ]
|
||||
def metadata
|
||||
@metadata ||= db_data.empty? ? DB::Version.metadata_at(number) : db_data
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def db_data
|
||||
@db_data ||= DB::Version.db_data(number)
|
||||
@db_data ||= DB::VulnApi.wordpress_data(number)
|
||||
end
|
||||
|
||||
# @return [ Array<Vulnerability> ]
|
||||
@@ -55,12 +62,12 @@ module WPScan
|
||||
|
||||
# @return [ String ]
|
||||
def release_date
|
||||
@release_date ||= db_data['release_date'] || 'Unknown'
|
||||
@release_date ||= metadata['release_date'] || 'Unknown'
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def status
|
||||
@status ||= db_data['status'] || 'Unknown'
|
||||
@status ||= metadata['status'] || 'Unknown'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
_______________________________________________________________
|
||||
__ _______ _____
|
||||
\ \ / / __ \ / ____|
|
||||
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
|
||||
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
|
||||
\ /\ / | | ____) | (__| (_| | | | |
|
||||
\/ \/ |_| |_____/ \___|\__,_|_| |_|
|
||||
__ _______ _____
|
||||
\ \ / / __ \ / ____|
|
||||
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
|
||||
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
|
||||
\ /\ / | | ____) | (__| (_| | | | |
|
||||
\/ \/ |_| |_____/ \___|\__,_|_| |_|
|
||||
|
||||
WordPress Security Scanner by the WPScan Team
|
||||
Version <%= WPScan::VERSION %>
|
||||
Sponsored by Sucuri - https://sucuri.net
|
||||
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
|
||||
WordPress Security Scanner by the WPScan Team
|
||||
Version <%= WPScan::VERSION %>
|
||||
<%= ' ' * ((63 - WPScan::DB::Sponsor.text.length)/2) + WPScan::DB::Sponsor.text %>
|
||||
@_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
|
||||
_______________________________________________________________
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<%= notice_icon %> Config Backup(s) Identified:
|
||||
<% @config_backups.each do |config_backup| -%>
|
||||
|
||||
<%= info_icon %> <%= config_backup %>
|
||||
<%= critical_icon %> <%= config_backup %>
|
||||
<%= render('@finding', item: config_backup) -%>
|
||||
<% end -%>
|
||||
<% end %>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<%= notice_icon %> Db Export(s) Identified:
|
||||
<% @db_exports.each do |db_export| -%>
|
||||
|
||||
<%= info_icon %> <%= db_export %>
|
||||
<%= critical_icon %> <%= db_export %>
|
||||
<%= render('@finding', item: db_export) -%>
|
||||
<% end -%>
|
||||
<% end %>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
| Detected By: <%= @item.found_by %>
|
||||
| Found By: <%= @item.found_by %>
|
||||
<% @item.interesting_entries.each do |entry| -%>
|
||||
| - <%= entry %>
|
||||
<% end -%>
|
||||
|
||||
13
app/views/cli/vuln_api/status.erb
Normal file
13
app/views/cli/vuln_api/status.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<% unless @status.empty? -%>
|
||||
<% if @status['http_error'] -%>
|
||||
<%= critical_icon %> WPVulnDB API, <%= @status['http_error'].to_s %>
|
||||
<% else -%>
|
||||
<%= info_icon %> WPVulnDB API OK
|
||||
| Plan: <%= @status['plan'] %>
|
||||
| Requests Done (during the scan): <%= @api_requests %>
|
||||
| Requests Remaining: <%= @status['requests_remaining'] %>
|
||||
<% end -%>
|
||||
<% else -%>
|
||||
<%= warning_icon %> No WPVulnDB API Token given, as a result vulnerability data has not been output.
|
||||
<%= warning_icon %> You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up
|
||||
<% end -%>
|
||||
@@ -5,7 +5,7 @@
|
||||
"@_WPScan_",
|
||||
"@ethicalhack3r",
|
||||
"@erwan_lr",
|
||||
"@_FireFart_"
|
||||
"@firefart"
|
||||
],
|
||||
"sponsored_by": "Sucuri - https://sucuri.net"
|
||||
"sponsor": <%= WPScan::DB::Sponsor.text.to_json %>
|
||||
},
|
||||
|
||||
13
app/views/json/vuln_api/status.erb
Normal file
13
app/views/json/vuln_api/status.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
"vuln_api": {
|
||||
<% unless @status.empty? -%>
|
||||
<% if @status['http_error'] -%>
|
||||
"http_error": <%= @status['http_error'].to_s.to_json %>
|
||||
<% else -%>
|
||||
"plan": <%= @status['plan'].to_json %>,
|
||||
"requests_done_during_scan": <%= @api_requests.to_json %>,
|
||||
"requests_remaining": <%= @status['requests_remaining'].to_json %>
|
||||
<% end -%>
|
||||
<% else -%>
|
||||
"error": "No WPVulnDB API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up"
|
||||
<% end -%>
|
||||
},
|
||||
@@ -5,6 +5,7 @@ require 'wpscan'
|
||||
|
||||
WPScan::Scan.new do |s|
|
||||
s.controllers <<
|
||||
WPScan::Controller::VulnApi.new <<
|
||||
WPScan::Controller::CustomDirectories.new <<
|
||||
WPScan::Controller::InterestingFindings.new <<
|
||||
WPScan::Controller::WpVersion.new <<
|
||||
|
||||
@@ -7,6 +7,7 @@ require 'wpscan'
|
||||
report = MemoryProfiler.report(top: 15) do
|
||||
WPScan::Scan.new do |s|
|
||||
s.controllers <<
|
||||
WPScan::Controller::VulnApi.new <<
|
||||
WPScan::Controller::CustomDirectories.new <<
|
||||
WPScan::Controller::InterestingFindings.new <<
|
||||
WPScan::Controller::WpVersion.new <<
|
||||
|
||||
@@ -12,6 +12,7 @@ StackProf.run(mode: :cpu, out: '/tmp/stackprof-cpu.dump', interval: 500) do
|
||||
# require_relative 'wpscan' doesn't work
|
||||
WPScan::Scan.new do |s|
|
||||
s.controllers <<
|
||||
WPScan::Controller::VulnApi.new <<
|
||||
WPScan::Controller::CustomDirectories.new <<
|
||||
WPScan::Controller::InterestingFindings.new <<
|
||||
WPScan::Controller::WpVersion.new <<
|
||||
|
||||
@@ -13,7 +13,8 @@ require 'uri'
|
||||
require 'time'
|
||||
require 'readline'
|
||||
require 'securerandom'
|
||||
|
||||
# Monkey Patches/Fixes/Override
|
||||
require 'wpscan/typhoeus/response' # Adds a from_vuln_api? method
|
||||
# Custom Libs
|
||||
require 'wpscan/helper'
|
||||
require 'wpscan/db'
|
||||
@@ -38,12 +39,28 @@ module WPScan
|
||||
APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path
|
||||
DB_DIR = Pathname.new(Dir.home).join('.wpscan', 'db')
|
||||
|
||||
Typhoeus.on_complete do |response|
|
||||
next if response.cached? || !response.from_vuln_api?
|
||||
|
||||
self.api_requests += 1
|
||||
end
|
||||
|
||||
# Override, otherwise it would be returned as 'wp_scan'
|
||||
#
|
||||
# @return [ String ]
|
||||
def self.app_name
|
||||
'wpscan'
|
||||
end
|
||||
|
||||
# @return [ Integer ]
|
||||
def self.api_requests
|
||||
@@api_requests ||= 0
|
||||
end
|
||||
|
||||
# @param [ Integer ] value
|
||||
def self.api_requests=(value)
|
||||
@@api_requests = value
|
||||
end
|
||||
end
|
||||
|
||||
require "#{WPScan::APP_DIR}/app"
|
||||
|
||||
@@ -7,7 +7,7 @@ module WPScan
|
||||
|
||||
# @return [ String ]
|
||||
def default_user_agent
|
||||
"WPScan v#{VERSION} (https://wpscan.org/)"
|
||||
@default_user_agent ||= "WPScan v#{VERSION} (https://wpscan.org/)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,9 +7,12 @@ require_relative 'db/plugins'
|
||||
require_relative 'db/themes'
|
||||
require_relative 'db/plugin'
|
||||
require_relative 'db/theme'
|
||||
require_relative 'db/sponsor'
|
||||
require_relative 'db/wp_version'
|
||||
require_relative 'db/fingerprints'
|
||||
|
||||
require_relative 'db/vuln_api'
|
||||
|
||||
require_relative 'db/dynamic_finders/base'
|
||||
require_relative 'db/dynamic_finders/plugin'
|
||||
require_relative 'db/dynamic_finders/theme'
|
||||
|
||||
@@ -5,18 +5,19 @@ module WPScan
|
||||
module DynamicFinders
|
||||
class Base
|
||||
# @return [ String ]
|
||||
def self.db_file
|
||||
@db_file ||= DB_DIR.join('dynamic_finders.yml').to_s
|
||||
def self.df_file
|
||||
@df_file ||= DB_DIR.join('dynamic_finders.yml').to_s
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.db_data
|
||||
# true allows aliases to be loaded
|
||||
@db_data ||= YAML.safe_load(File.read(db_file), [Regexp], [], true)
|
||||
def self.all_df_data
|
||||
@all_df_data ||= YAML.safe_load(File.read(df_file), [Regexp])
|
||||
end
|
||||
|
||||
# @return [ Array<Symbol> ]
|
||||
def self.allowed_classes
|
||||
# The Readme is not put in there as it's not a Real DF, but rather using the DF system
|
||||
# to get the list of potential filenames for a given slug
|
||||
@allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser]
|
||||
end
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ module WPScan
|
||||
module DynamicFinders
|
||||
class Plugin < Base
|
||||
# @return [ Hash ]
|
||||
def self.db_data
|
||||
@db_data ||= super['plugins'] || {}
|
||||
def self.df_data
|
||||
@df_data ||= all_df_data['plugins'] || {}
|
||||
end
|
||||
|
||||
def self.version_finder_module
|
||||
@@ -21,7 +21,7 @@ module WPScan
|
||||
|
||||
return configs unless allowed_classes.include?(finder_class)
|
||||
|
||||
db_data.each do |slug, finders|
|
||||
df_data.each do |slug, finders|
|
||||
# Quite sure better can be done with some kind of logic statement in the select
|
||||
fs = if aggressive
|
||||
finders.reject { |_f, c| c['path'].nil? }
|
||||
@@ -48,7 +48,7 @@ module WPScan
|
||||
|
||||
@versions_finders_configs = {}
|
||||
|
||||
db_data.each do |slug, finders|
|
||||
df_data.each do |slug, finders|
|
||||
finders.each do |finder_name, config|
|
||||
next unless config.key?('version')
|
||||
|
||||
@@ -73,23 +73,33 @@ module WPScan
|
||||
version_finder_module.const_get(constant_name)
|
||||
end
|
||||
|
||||
def self.create_versions_finders
|
||||
versions_finders_configs.each do |slug, finders|
|
||||
mod = maybe_create_module(slug)
|
||||
# Create the dynamic finders related to the given slug, and return the created classes
|
||||
#
|
||||
# @param [ String ] slug
|
||||
#
|
||||
# @return [ Array<Class> ] The created classes
|
||||
def self.create_versions_finders(slug)
|
||||
created = []
|
||||
mod = maybe_create_module(slug)
|
||||
|
||||
finders.each do |finder_class, config|
|
||||
klass = config['class'] || finder_class
|
||||
versions_finders_configs[slug]&.each do |finder_class, config|
|
||||
klass = config['class'] || finder_class
|
||||
|
||||
# Instead of raising exceptions, skip unallowed/already defined finders
|
||||
# So that, when new DF configs are put in the .yml
|
||||
# users with old version of WPScan will still be able to scan blogs
|
||||
# when updating the DB but not the tool
|
||||
next if mod.constants.include?(finder_class.to_sym) ||
|
||||
!allowed_classes.include?(klass.to_sym)
|
||||
# Instead of raising exceptions, skip unallowed/already defined finders
|
||||
# So that, when new DF configs are put in the .yml
|
||||
# users with old version of WPScan will still be able to scan blogs
|
||||
# when updating the DB but not the tool
|
||||
|
||||
version_finder_super_class(klass).create_child_class(mod, finder_class.to_sym, config)
|
||||
end
|
||||
next unless allowed_classes.include?(klass.to_sym)
|
||||
|
||||
created << if mod.constants.include?(finder_class.to_sym)
|
||||
mod.const_get(finder_class.to_sym)
|
||||
else
|
||||
version_finder_super_class(klass).create_child_class(mod, finder_class.to_sym, config)
|
||||
end
|
||||
end
|
||||
|
||||
created
|
||||
end
|
||||
|
||||
# The idea here would be to check if the class exist in
|
||||
|
||||
@@ -5,8 +5,8 @@ module WPScan
|
||||
module DynamicFinders
|
||||
class Theme < Plugin
|
||||
# @return [ Hash ]
|
||||
def self.db_data
|
||||
@db_data ||= super['themes'] || {}
|
||||
def self.df_data
|
||||
@df_data ||= all_df_data['themes'] || {}
|
||||
end
|
||||
|
||||
def self.version_finder_module
|
||||
|
||||
@@ -5,8 +5,8 @@ module WPScan
|
||||
module DynamicFinders
|
||||
class Wordpress < Base
|
||||
# @return [ Hash ]
|
||||
def self.db_data
|
||||
@db_data ||= super['wordpress'] || {}
|
||||
def self.df_data
|
||||
@df_data ||= all_df_data['wordpress'] || {}
|
||||
end
|
||||
|
||||
# @return [ Constant ]
|
||||
@@ -30,9 +30,9 @@ module WPScan
|
||||
return configs unless allowed_classes.include?(finder_class)
|
||||
|
||||
finders = if aggressive
|
||||
db_data.reject { |_f, c| c['path'].nil? }
|
||||
df_data.reject { |_f, c| c['path'].nil? }
|
||||
else
|
||||
db_data.select { |_f, c| c['path'].nil? }
|
||||
df_data.select { |_f, c| c['path'].nil? }
|
||||
end
|
||||
|
||||
finders.each do |finder_name, config|
|
||||
@@ -48,7 +48,7 @@ module WPScan
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.versions_finders_configs
|
||||
@versions_finders_configs ||= db_data.select { |_finder_name, config| config.key?('version') }
|
||||
@versions_finders_configs ||= df_data.select { |_finder_name, config| config.key?('version') }
|
||||
end
|
||||
|
||||
def self.create_versions_finders
|
||||
|
||||
@@ -4,9 +4,9 @@ module WPScan
|
||||
module DB
|
||||
# Plugin DB
|
||||
class Plugin < WpItem
|
||||
# @return [ String ]
|
||||
def self.db_file
|
||||
@db_file ||= DB_DIR.join('plugins.json').to_s
|
||||
# @return [ Hash ]
|
||||
def self.metadata
|
||||
@metadata ||= super['plugins'] || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,8 +5,8 @@ module WPScan
|
||||
# WP Plugins
|
||||
class Plugins < WpItems
|
||||
# @return [ JSON ]
|
||||
def self.db
|
||||
Plugin.db
|
||||
def self.metadata
|
||||
Plugin.metadata
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
16
lib/wpscan/db/sponsor.rb
Normal file
16
lib/wpscan/db/sponsor.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module DB
|
||||
class Sponsor
|
||||
# @return [ Hash ]
|
||||
def self.text
|
||||
@text ||= file_path.exist? ? File.read(file_path).chomp : ''
|
||||
end
|
||||
|
||||
def self.file_path
|
||||
@file_path ||= DB_DIR.join('sponsor.txt')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,9 +4,9 @@ module WPScan
|
||||
module DB
|
||||
# Theme DB
|
||||
class Theme < WpItem
|
||||
# @return [ String ]
|
||||
def self.db_file
|
||||
@db_file ||= DB_DIR.join('themes.json').to_s
|
||||
# @return [ Hash ]
|
||||
def self.metadata
|
||||
@metadata ||= super['themes'] || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,8 +5,8 @@ module WPScan
|
||||
# WP Themes
|
||||
class Themes < WpItems
|
||||
# @return [ JSON ]
|
||||
def self.db
|
||||
Theme.db
|
||||
def self.metadata
|
||||
Theme.metadata
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,12 +7,15 @@ module WPScan
|
||||
class Updater
|
||||
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
|
||||
FILES = %w[
|
||||
plugins.json themes.json wordpresses.json
|
||||
metadata.json wp_fingerprints.json
|
||||
timthumbs-v3.txt config_backups.txt db_exports.txt
|
||||
dynamic_finders.yml wp_fingerprints.json LICENSE
|
||||
dynamic_finders.yml LICENSE sponsor.txt
|
||||
].freeze
|
||||
|
||||
OLD_FILES = %w[wordpress.db user-agents.txt dynamic_finders_01.yml].freeze
|
||||
OLD_FILES = %w[
|
||||
wordpress.db user-agents.txt dynamic_finders_01.yml
|
||||
wordpresses.json plugins.json themes.json
|
||||
].freeze
|
||||
|
||||
attr_reader :repo_directory
|
||||
|
||||
@@ -64,11 +67,12 @@ module WPScan
|
||||
# @return [ Hash ] The params for Typhoeus::Request
|
||||
# @note Those params can't be overriden by CLI options
|
||||
def request_params
|
||||
{
|
||||
@request_params ||= {
|
||||
timeout: 600,
|
||||
connecttimeout: 300,
|
||||
accept_encoding: 'gzip, deflate',
|
||||
cache_ttl: 0
|
||||
cache_ttl: 0,
|
||||
headers: { 'User-Agent' => Browser.instance.default_user_agent, 'Referer' => nil }
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
79
lib/wpscan/db/vuln_api.rb
Normal file
79
lib/wpscan/db/vuln_api.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module DB
|
||||
# WPVulnDB API
|
||||
class VulnApi
|
||||
NON_ERROR_CODES = [200, 401].freeze
|
||||
|
||||
class << self
|
||||
attr_accessor :token
|
||||
end
|
||||
|
||||
# @return [ Addressable::URI ]
|
||||
def self.uri
|
||||
@uri ||= Addressable::URI.parse('https://wpvulndb.com/api/v3/')
|
||||
end
|
||||
|
||||
# @param [ String ] path
|
||||
# @param [ Hash ] params
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def self.get(path, params = {})
|
||||
return {} unless token
|
||||
|
||||
res = Browser.get(uri.join(path), params.merge(request_params))
|
||||
|
||||
return {} if res.code == 404 # This is for API inconsistencies when dots in path
|
||||
return JSON.parse(res.body) if NON_ERROR_CODES.include?(res.code)
|
||||
|
||||
raise Error::HTTP, res
|
||||
rescue Error::HTTP => e
|
||||
retries ||= 0
|
||||
|
||||
if (retries += 1) <= 3
|
||||
sleep(1)
|
||||
retry
|
||||
end
|
||||
|
||||
{ 'http_error' => e }
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.plugin_data(slug)
|
||||
get("plugins/#{slug}")&.dig(slug) || {}
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.theme_data(slug)
|
||||
get("themes/#{slug}")&.dig(slug) || {}
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.wordpress_data(version_number)
|
||||
get("wordpresses/#{version_number.tr('.', '')}")&.dig(version_number) || {}
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.status
|
||||
json = get('status', params: { version: WPScan::VERSION }, cache_ttl: 0)
|
||||
|
||||
json['requests_remaining'] = 'Unlimited' if json['requests_remaining'] == -1
|
||||
|
||||
json
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.request_params
|
||||
{
|
||||
headers: {
|
||||
'Host' => uri.host, # Reset in case user provided a --vhost for the target
|
||||
'Referer' => nil, # Removes referer set by the cmsscanner to the target url
|
||||
'User-Agent' => Browser.instance.default_user_agent,
|
||||
'Authorization' => "Token token=#{token}"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,14 +6,19 @@ module WPScan
|
||||
class WpItem
|
||||
# @param [ String ] identifier The plugin/theme slug or version number
|
||||
#
|
||||
# @return [ Hash ] The JSON data from the DB associated to the identifier
|
||||
def self.db_data(identifier)
|
||||
db[identifier] || {}
|
||||
# @return [ Hash ] The JSON data from the metadata associated to the identifier
|
||||
def self.metadata_at(identifier)
|
||||
metadata[identifier] || {}
|
||||
end
|
||||
|
||||
# @return [ JSON ]
|
||||
def self.db
|
||||
@db ||= read_json_file(db_file)
|
||||
def self.metadata
|
||||
@metadata ||= read_json_file(metadata_file)
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def self.metadata_file
|
||||
@metadata_file ||= DB_DIR.join('metadata.json').to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,17 +6,17 @@ module WPScan
|
||||
class WpItems
|
||||
# @return [ Array<String> ] The slug of all items
|
||||
def self.all_slugs
|
||||
db.keys
|
||||
metadata.keys
|
||||
end
|
||||
|
||||
# @return [ Array<String> ] The slug of all popular items
|
||||
def self.popular_slugs
|
||||
db.select { |_key, item| item['popular'] == true }.keys
|
||||
metadata.select { |_key, item| item['popular'] == true }.keys
|
||||
end
|
||||
|
||||
# @return [ Array<String> ] The slug of all vulnerable items
|
||||
def self.vulnerable_slugs
|
||||
db.reject { |_key, item| item['vulnerabilities'].empty? }.keys
|
||||
metadata.select { |_key, item| item['vulnerabilities'] == true }.keys
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,9 +4,9 @@ module WPScan
|
||||
module DB
|
||||
# WP Version
|
||||
class Version < WpItem
|
||||
# @return [ String ]
|
||||
def self.db_file
|
||||
@db_file ||= DB_DIR.join('wordpresses.json').to_s
|
||||
# @return [ Hash ]
|
||||
def self.metadata
|
||||
@metadata ||= super['wordpress'] || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,9 @@ module WPScan
|
||||
end
|
||||
end
|
||||
|
||||
require_relative 'errors/enumeration'
|
||||
require_relative 'errors/http'
|
||||
require_relative 'errors/update'
|
||||
require_relative 'errors/vuln_api'
|
||||
require_relative 'errors/wordpress'
|
||||
require_relative 'errors/xmlrpc'
|
||||
|
||||
21
lib/wpscan/errors/enumeration.rb
Normal file
21
lib/wpscan/errors/enumeration.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Error
|
||||
class PluginsThresholdReached < Standard
|
||||
def to_s
|
||||
"The number of plugins detected reached the threshold of #{ParsedCli.plugins_threshold} " \
|
||||
'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
|
||||
'option to ignore the bad responses.'
|
||||
end
|
||||
end
|
||||
|
||||
class ThemesThresholdReached < Standard
|
||||
def to_s
|
||||
"The number of themes detected reached the threshold of #{ParsedCli.themes_threshold} " \
|
||||
'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
|
||||
'option to ignore the bad responses.'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
lib/wpscan/errors/vuln_api.rb
Normal file
20
lib/wpscan/errors/vuln_api.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Error
|
||||
# Error raised when the token given via --api-token is invalid
|
||||
class InvalidApiToken < Standard
|
||||
def to_s
|
||||
'The API token provided is invalid'
|
||||
end
|
||||
end
|
||||
|
||||
# Error raised when the number of API requests has been reached
|
||||
# currently not implemented on the API side
|
||||
class ApiLimitReached < Standard
|
||||
def to_s
|
||||
'Your API limit has been reached'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -44,19 +44,27 @@ module WPScan
|
||||
#
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Mixed ]
|
||||
# @return [ Mixed: nil, Object, Array ]
|
||||
def find(_response, _opts = {})
|
||||
raise NoMethodError
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Mixed ] See #find
|
||||
def passive(opts = {})
|
||||
return if self.class::PATH
|
||||
|
||||
find(target.homepage_res, opts)
|
||||
homepage_result = find(target.homepage_res, opts)
|
||||
|
||||
if homepage_result
|
||||
return homepage_result unless homepage_result.is_a?(Array) && homepage_result.empty?
|
||||
end
|
||||
|
||||
find(target.error_404_res, opts)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Mixed ] See #find
|
||||
def aggressive(opts = {})
|
||||
return unless self.class::PATH
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ module WPScan
|
||||
module Finders
|
||||
module DynamicFinder
|
||||
module Version
|
||||
# Version finder using Body Pattern method. Tipically used when the response is not
|
||||
# Version finder using Body Pattern method. Typically used when the response is not
|
||||
# an HTML doc and Xpath can't be used
|
||||
class BodyPattern < WPScan::Finders::DynamicFinder::Version::Finder
|
||||
class BodyPattern < Finders::DynamicFinder::Version::Finder
|
||||
# @return [ Hash ]
|
||||
def self.child_class_constants
|
||||
@child_class_constants ||= super().merge(PATTERN: nil, CONFIDENCE: 60)
|
||||
@@ -16,7 +16,7 @@ module WPScan
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Version ]
|
||||
def find(response, _opts = {})
|
||||
return unless response.body =~ self.class::PATTERN
|
||||
return unless response.code != 404 && response.body =~ self.class::PATTERN
|
||||
|
||||
create_version(
|
||||
Regexp.last_match[:v],
|
||||
|
||||
@@ -6,7 +6,7 @@ module WPScan
|
||||
module Version
|
||||
# Version finder in Comment, which is basically an Xpath one with a default
|
||||
# Xpath of //comment()
|
||||
class Comment < WPScan::Finders::DynamicFinder::Version::Xpath
|
||||
class Comment < Finders::DynamicFinder::Version::Xpath
|
||||
# @return [ Hash ]
|
||||
def self.child_class_constants
|
||||
@child_class_constants ||= super().merge(PATTERN: nil, XPATH: '//comment()')
|
||||
|
||||
@@ -6,7 +6,7 @@ module WPScan
|
||||
module Version
|
||||
# Version finder using by parsing config files, such as composer.json
|
||||
# and so on
|
||||
class ConfigParser < WPScan::Finders::DynamicFinder::Version::Finder
|
||||
class ConfigParser < Finders::DynamicFinder::Version::Finder
|
||||
ALLOWED_PARSERS = [JSON, YAML].freeze
|
||||
|
||||
def self.child_class_constants
|
||||
|
||||
@@ -5,7 +5,7 @@ module WPScan
|
||||
module DynamicFinder
|
||||
module Version
|
||||
# Version finder using Header Pattern method
|
||||
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::Finder
|
||||
class HeaderPattern < Finders::DynamicFinder::Version::Finder
|
||||
# @return [ Hash ]
|
||||
def self.child_class_constants
|
||||
@child_class_constants ||= super().merge(HEADER: nil, PATTERN: nil, CONFIDENCE: 60)
|
||||
|
||||
@@ -5,7 +5,7 @@ module WPScan
|
||||
module DynamicFinder
|
||||
module Version
|
||||
# Version finder using JavaScript Variable method
|
||||
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::Finder
|
||||
class JavascriptVar < Finders::DynamicFinder::Version::Finder
|
||||
# @return [ Hash ]
|
||||
def self.child_class_constants
|
||||
@child_class_constants ||= super().merge(
|
||||
|
||||
@@ -5,7 +5,7 @@ module WPScan
|
||||
module DynamicFinder
|
||||
module Version
|
||||
# Version finder using QueryParameter method
|
||||
class QueryParameter < WPScan::Finders::DynamicFinder::Version::Finder
|
||||
class QueryParameter < Finders::DynamicFinder::Version::Finder
|
||||
# @return [ Hash ]
|
||||
def self.child_class_constants
|
||||
@child_class_constants ||= super().merge(
|
||||
|
||||
@@ -5,7 +5,7 @@ module WPScan
|
||||
module DynamicFinder
|
||||
module Version
|
||||
# Version finder using Xpath method
|
||||
class Xpath < WPScan::Finders::DynamicFinder::Version::Finder
|
||||
class Xpath < Finders::DynamicFinder::Version::Finder
|
||||
# @return [ Hash ]
|
||||
def self.child_class_constants
|
||||
@child_class_constants ||= super().merge(
|
||||
|
||||
@@ -4,22 +4,22 @@ module WPScan
|
||||
module Finders
|
||||
module DynamicFinder
|
||||
module WpItemVersion
|
||||
class BodyPattern < WPScan::Finders::DynamicFinder::Version::BodyPattern
|
||||
class BodyPattern < Finders::DynamicFinder::Version::BodyPattern
|
||||
end
|
||||
|
||||
class Comment < WPScan::Finders::DynamicFinder::Version::Comment
|
||||
class Comment < Finders::DynamicFinder::Version::Comment
|
||||
end
|
||||
|
||||
class ConfigParser < WPScan::Finders::DynamicFinder::Version::ConfigParser
|
||||
class ConfigParser < Finders::DynamicFinder::Version::ConfigParser
|
||||
end
|
||||
|
||||
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::HeaderPattern
|
||||
class HeaderPattern < Finders::DynamicFinder::Version::HeaderPattern
|
||||
end
|
||||
|
||||
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::JavascriptVar
|
||||
class JavascriptVar < Finders::DynamicFinder::Version::JavascriptVar
|
||||
end
|
||||
|
||||
class QueryParameter < WPScan::Finders::DynamicFinder::Version::QueryParameter
|
||||
class QueryParameter < Finders::DynamicFinder::Version::QueryParameter
|
||||
# @return [ Regexp ]
|
||||
def path_pattern
|
||||
# TODO: consider the target.blog.themes_dir if the target is a Theme (maybe implement a WpItem#item_dir ?)
|
||||
@@ -37,7 +37,7 @@ module WPScan
|
||||
end
|
||||
end
|
||||
|
||||
class Xpath < WPScan::Finders::DynamicFinder::Version::Xpath
|
||||
class Xpath < Finders::DynamicFinder::Version::Xpath
|
||||
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