Compare commits
154 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b68bdb36c | ||
|
|
fb82538441 | ||
|
|
2709d0869a | ||
|
|
343f87bbe7 | ||
|
|
ecbfc6004c | ||
|
|
c57eecc81b | ||
|
|
7ea14dc03f | ||
|
|
4340d27258 | ||
|
|
e911be8f14 | ||
|
|
a4c650cdff | ||
|
|
31a58f8a8f | ||
|
|
ba4f15f111 | ||
|
|
206a913eb9 | ||
|
|
21ba490073 | ||
|
|
2a29e2ed95 | ||
|
|
9517d14fd3 | ||
|
|
3deaa896df | ||
|
|
c117007dc0 | ||
|
|
50baa238b9 | ||
|
|
0e2d771660 | ||
|
|
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 | ||
|
|
84422b10c8 | ||
|
|
d05ad0f8f4 | ||
|
|
3f70ddaffa |
@@ -14,3 +14,4 @@ Dockerfile
|
|||||||
*.orig
|
*.orig
|
||||||
bin/wpscan-*
|
bin/wpscan-*
|
||||||
.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
|
--require spec_helper
|
||||||
--fail-fast
|
--color
|
||||||
--require spec_helper
|
|
||||||
17
.rubocop.yml
17
.rubocop.yml
@@ -4,14 +4,8 @@ AllCops:
|
|||||||
Exclude:
|
Exclude:
|
||||||
- '*.gemspec'
|
- '*.gemspec'
|
||||||
- 'vendor/**/*'
|
- 'vendor/**/*'
|
||||||
ClassVars:
|
Layout/LineLength:
|
||||||
Enabled: false
|
|
||||||
LineLength:
|
|
||||||
Max: 120
|
Max: 120
|
||||||
MethodLength:
|
|
||||||
Max: 20
|
|
||||||
Exclude:
|
|
||||||
- 'app/controllers/enumeration/cli_options.rb'
|
|
||||||
Lint/UriEscapeUnescape:
|
Lint/UriEscapeUnescape:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
@@ -25,7 +19,16 @@ Metrics/ClassLength:
|
|||||||
- 'app/controllers/enumeration/cli_options.rb'
|
- 'app/controllers/enumeration/cli_options.rb'
|
||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 8
|
Max: 8
|
||||||
|
Metrics/MethodLength:
|
||||||
|
Max: 20
|
||||||
|
Exclude:
|
||||||
|
- 'app/controllers/enumeration/cli_options.rb'
|
||||||
|
Style/ClassVars:
|
||||||
|
Enabled: false
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Style/FormatStringToken:
|
Style/FormatStringToken:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
Style/NumericPredicate:
|
||||||
|
Exclude:
|
||||||
|
- 'app/controllers/vuln_api.rb'
|
||||||
|
|||||||
27
.travis.yml
27
.travis.yml
@@ -2,29 +2,10 @@ language: ruby
|
|||||||
sudo: false
|
sudo: false
|
||||||
cache: bundler
|
cache: bundler
|
||||||
rvm:
|
rvm:
|
||||||
- 2.4.1
|
- 2.4.9
|
||||||
- 2.4.2
|
- 2.5.7
|
||||||
- 2.4.3
|
- 2.6.5
|
||||||
- 2.4.4
|
- 2.7.0
|
||||||
- 2.4.5
|
|
||||||
- 2.4.6
|
|
||||||
- 2.5.0
|
|
||||||
- 2.5.1
|
|
||||||
- 2.5.2
|
|
||||||
- 2.5.3
|
|
||||||
- 2.5.4
|
|
||||||
- 2.5.5
|
|
||||||
- 2.6.0
|
|
||||||
- 2.6.1
|
|
||||||
- 2.6.2
|
|
||||||
- 2.6.3
|
|
||||||
- ruby-head
|
|
||||||
before_install:
|
|
||||||
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
|
||||||
- gem update --system
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- rvm: ruby-head
|
|
||||||
script:
|
script:
|
||||||
- bundle exec rubocop
|
- bundle exec rubocop
|
||||||
- bundle exec rspec
|
- bundle exec rspec
|
||||||
|
|||||||
@@ -38,4 +38,3 @@ USER wpscan
|
|||||||
RUN /usr/local/bundle/bin/wpscan --update --verbose
|
RUN /usr/local/bundle/bin/wpscan --update --verbose
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]
|
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]
|
||||||
CMD ["--help"]
|
|
||||||
|
|||||||
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.
|
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
||||||
|
|
||||||
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
|
||||||
|
|
||||||
Free-use Terms and Conditions;
|
Free-use Terms and Conditions;
|
||||||
|
|
||||||
3. Redistribution
|
3. Redistribution
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -77,41 +77,60 @@ docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u1-1
|
|||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings. If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings.
|
||||||
|
|
||||||
|
If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
||||||
As a result, when using the ```--enumerate``` option, don't forget to set the ```--plugins-detection``` accordingly, as its default is 'passive'.
|
As a result, when using the ```--enumerate``` option, don't forget to set the ```--plugins-detection``` accordingly, as its default is 'passive'.
|
||||||
|
|
||||||
For more options, open a terminal and type ```wpscan --help``` (if you built wpscan from the source, you should type the command outside of the git repo)
|
For more options, open a terminal and type ```wpscan --help``` (if you built wpscan from the source, you should type the command outside of the git repo)
|
||||||
|
|
||||||
The DB is located at ~/.wpscan/db
|
The DB is located at ~/.wpscan/db
|
||||||
|
|
||||||
|
## Vulnerability Database
|
||||||
|
|
||||||
|
The WPScan CLI tool uses the [WPVulnDB API](https://wpvulndb.com/api) to retrieve WordPress vulnerability data in real time. For WPScan to retrieve the vulnerability data an API token must be supplied via the `--api-token` option, or via a configuration file, as discussed below. An API token can be obtained by registering an account on [WPVulnDB](https://wpvulndb.com/users/sign_up). Up to 50 API requests per day are given free of charge to registered users. Once the 50 API requests are exhausted, WPScan will continue to work as normal but without any vulnerability data. Users can upgrade to paid API usage to increase their API limits within their user profile on [WPVulnDB](https://wpvulndb.com/).
|
||||||
|
|
||||||
|
## Load CLI options from file/s
|
||||||
|
|
||||||
WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
|
WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
|
||||||
|
|
||||||
- ~/.wpscan/cli_options.json
|
- ~/.wpscan/scan.json
|
||||||
- ~/.wpscan/cli_options.yml
|
- ~/.wpscan/scan.yml
|
||||||
- pwd/.wpscan/cli_options.json
|
- pwd/.wpscan/scan.json
|
||||||
- pwd/.wpscan/cli_options.yml
|
- pwd/.wpscan/scan.yml
|
||||||
|
|
||||||
If those files exist, options from them will be loaded and overridden if found twice.
|
If those files exist, options from the `cli_options` key will be loaded and overridden if found twice.
|
||||||
|
|
||||||
e.g:
|
e.g:
|
||||||
|
|
||||||
~/.wpscan/cli_options.yml:
|
~/.wpscan/scan.yml:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
proxy: 'http://127.0.0.1:8080'
|
cli_options:
|
||||||
verbose: true
|
proxy: 'http://127.0.0.1:8080'
|
||||||
|
verbose: true
|
||||||
```
|
```
|
||||||
|
|
||||||
pwd/.wpscan/cli_options.yml:
|
pwd/.wpscan/scan.yml:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
proxy: 'socks5://127.0.0.1:9090'
|
cli_options:
|
||||||
url: 'http://target.tld'
|
proxy: 'socks5://127.0.0.1:9090'
|
||||||
|
url: 'http://target.tld'
|
||||||
```
|
```
|
||||||
|
|
||||||
Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
|
Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
|
||||||
|
|
||||||
Enumerating usernames
|
## Save API Token in a file
|
||||||
|
|
||||||
|
The feature mentioned above is useful to keep the API Token in a config file and not have to supply it via the CLI each time. To do so, create the ~/.wpscan/scan.yml file containing the below:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
cli_options:
|
||||||
|
api_token: YOUR_API_TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enumerating usernames
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
wpscan --url https://target.tld/ --enumerate u
|
wpscan --url https://target.tld/ --enumerate u
|
||||||
|
|||||||
6
Rakefile
6
Rakefile
@@ -6,14 +6,18 @@ exec = []
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
require 'rubocop/rake_task'
|
require 'rubocop/rake_task'
|
||||||
|
|
||||||
RuboCop::RakeTask.new
|
RuboCop::RakeTask.new
|
||||||
|
|
||||||
exec << :rubocop
|
exec << :rubocop
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
require 'rspec/core/rake_task'
|
require 'rspec/core/rake_task'
|
||||||
RSpec::Core::RakeTask.new(:spec)
|
|
||||||
|
RSpec::Core::RakeTask.new(:spec) { |t| t.rspec_opts = %w{--tag ~slow} }
|
||||||
|
|
||||||
exec << :spec
|
exec << :spec
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'controllers/core'
|
require_relative 'controllers/core'
|
||||||
|
require_relative 'controllers/vuln_api'
|
||||||
require_relative 'controllers/custom_directories'
|
require_relative 'controllers/custom_directories'
|
||||||
require_relative 'controllers/wp_version'
|
require_relative 'controllers/wp_version'
|
||||||
require_relative 'controllers/main_theme'
|
require_relative 'controllers/main_theme'
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module WPScan
|
|||||||
target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir
|
target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir
|
||||||
target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir
|
target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir
|
||||||
|
|
||||||
return if target.content_dir(ParsedCli.detection_mode)
|
return if target.content_dir
|
||||||
|
|
||||||
raise Error::WpContentDirNotDetected
|
raise Error::WpContentDirNotDetected
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ module WPScan
|
|||||||
choices: {
|
choices: {
|
||||||
vp: OptBoolean.new(['--vulnerable-plugins']),
|
vp: OptBoolean.new(['--vulnerable-plugins']),
|
||||||
ap: OptBoolean.new(['--all-plugins']),
|
ap: OptBoolean.new(['--all-plugins']),
|
||||||
p: OptBoolean.new(['--plugins']),
|
p: OptBoolean.new(['--popular-plugins']),
|
||||||
vt: OptBoolean.new(['--vulnerable-themes']),
|
vt: OptBoolean.new(['--vulnerable-themes']),
|
||||||
at: OptBoolean.new(['--all-themes']),
|
at: OptBoolean.new(['--all-themes']),
|
||||||
t: OptBoolean.new(['--themes']),
|
t: OptBoolean.new(['--popular-themes']),
|
||||||
tt: OptBoolean.new(['--timthumbs']),
|
tt: OptBoolean.new(['--timthumbs']),
|
||||||
cb: OptBoolean.new(['--config-backups']),
|
cb: OptBoolean.new(['--config-backups']),
|
||||||
dbe: OptBoolean.new(['--db-exports']),
|
dbe: OptBoolean.new(['--db-exports']),
|
||||||
@@ -69,7 +69,7 @@ module WPScan
|
|||||||
OptInteger.new(
|
OptInteger.new(
|
||||||
['--plugins-threshold THRESHOLD',
|
['--plugins-threshold THRESHOLD',
|
||||||
'Raise an error when the number of detected plugins via known locations reaches the 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
|
'Set to 0 to ignore the threshold.'], default: 100, advanced: true
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@@ -98,7 +98,7 @@ module WPScan
|
|||||||
OptInteger.new(
|
OptInteger.new(
|
||||||
['--themes-threshold THRESHOLD',
|
['--themes-threshold THRESHOLD',
|
||||||
'Raise an error when the number of detected themes via known locations reaches the 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
|
'Set to 0 to ignore the threshold.'], default: 20, advanced: true
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Boolean ] Wether or not to enumerate the plugins
|
# @return [ Boolean ] Wether or not to enumerate the plugins
|
||||||
def enum_plugins?(opts)
|
def enum_plugins?(opts)
|
||||||
opts[:plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
opts[:popular_plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_plugins
|
def enum_plugins
|
||||||
@@ -92,7 +92,7 @@ module WPScan
|
|||||||
|
|
||||||
if opts[:enumerate][:all_plugins]
|
if opts[:enumerate][:all_plugins]
|
||||||
DB::Plugins.all_slugs
|
DB::Plugins.all_slugs
|
||||||
elsif opts[:enumerate][:plugins]
|
elsif opts[:enumerate][:popular_plugins]
|
||||||
DB::Plugins.popular_slugs
|
DB::Plugins.popular_slugs
|
||||||
else
|
else
|
||||||
DB::Plugins.vulnerable_slugs
|
DB::Plugins.vulnerable_slugs
|
||||||
@@ -103,7 +103,7 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Boolean ] Wether or not to enumerate the themes
|
# @return [ Boolean ] Wether or not to enumerate the themes
|
||||||
def enum_themes?(opts)
|
def enum_themes?(opts)
|
||||||
opts[:themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
opts[:popular_themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_themes
|
def enum_themes
|
||||||
@@ -139,7 +139,7 @@ module WPScan
|
|||||||
|
|
||||||
if opts[:enumerate][:all_themes]
|
if opts[:enumerate][:all_themes]
|
||||||
DB::Themes.all_slugs
|
DB::Themes.all_slugs
|
||||||
elsif opts[:enumerate][:themes]
|
elsif opts[:enumerate][:popular_themes]
|
||||||
DB::Themes.popular_slugs
|
DB::Themes.popular_slugs
|
||||||
else
|
else
|
||||||
DB::Themes.vulnerable_slugs
|
DB::Themes.vulnerable_slugs
|
||||||
|
|||||||
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
|
||||||
@@ -4,7 +4,6 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module DbExports
|
module DbExports
|
||||||
# DB Exports finder
|
# DB Exports finder
|
||||||
# See https://github.com/wpscanteam/wpscan-v3/issues/62
|
|
||||||
class KnownLocations < CMSScanner::Finders::Finder
|
class KnownLocations < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ module WPScan
|
|||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def potential_urls(opts = {})
|
def potential_urls(opts = {})
|
||||||
urls = {}
|
urls = {}
|
||||||
domain_name = target.uri.host[/(^[\w|-]+)/, 1]
|
domain_name = PublicSuffix.domain(target.uri.host)[/(^[\w|-]+)/, 1]
|
||||||
|
|
||||||
File.open(opts[:list]).each_with_index do |path, index|
|
File.open(opts[:list]).each_with_index do |path, index|
|
||||||
path.gsub!('{domain_name}', domain_name)
|
path.gsub!('{domain_name}', domain_name)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'main_theme/css_style'
|
require_relative 'main_theme/css_style_in_homepage'
|
||||||
|
require_relative 'main_theme/css_style_in_404_page'
|
||||||
require_relative 'main_theme/woo_framework_meta_generator'
|
require_relative 'main_theme/woo_framework_meta_generator'
|
||||||
require_relative 'main_theme/urls_in_homepage'
|
require_relative 'main_theme/urls_in_homepage'
|
||||||
|
require_relative 'main_theme/urls_in_404_page'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
@@ -14,9 +16,11 @@ module WPScan
|
|||||||
# @param [ WPScan::Target ] target
|
# @param [ WPScan::Target ] target
|
||||||
def initialize(target)
|
def initialize(target)
|
||||||
finders <<
|
finders <<
|
||||||
MainTheme::CssStyle.new(target) <<
|
MainTheme::CssStyleInHomepage.new(target) <<
|
||||||
|
MainTheme::CssStyleIn404Page.new(target) <<
|
||||||
MainTheme::WooFrameworkMetaGenerator.new(target) <<
|
MainTheme::WooFrameworkMetaGenerator.new(target) <<
|
||||||
MainTheme::UrlsInHomepage.new(target)
|
MainTheme::UrlsInHomepage.new(target) <<
|
||||||
|
MainTheme::UrlsIn404Page.new(target)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
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 WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module MainTheme
|
module MainTheme
|
||||||
# From the css style
|
# From the CSS style in the homepage
|
||||||
class CssStyle < CMSScanner::Finders::Finder
|
class CssStyleInHomepage < CMSScanner::Finders::Finder
|
||||||
include Finders::WpItems::URLsInHomepage
|
include Finders::WpItems::UrlsInPage # To have the item_code_pattern method available here
|
||||||
|
|
||||||
def create_theme(slug, style_url, opts)
|
def create_theme(slug, style_url, opts)
|
||||||
Model::Theme.new(
|
Model::Theme.new(
|
||||||
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
|
module MainTheme
|
||||||
# URLs In Homepage Finder
|
# URLs In Homepage Finder
|
||||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||||
include WpItems::URLsInHomepage
|
include WpItems::UrlsInPage
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
#
|
#
|
||||||
@@ -21,6 +21,11 @@ module WPScan
|
|||||||
|
|
||||||
found
|
found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ Typhoeus::Response ]
|
||||||
|
def page_res
|
||||||
|
@page_res ||= target.homepage_res
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module WPScan
|
|||||||
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze
|
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze
|
||||||
|
|
||||||
def passive(opts = {})
|
def passive(opts = {})
|
||||||
return unless target.homepage_res.body =~ PATTERN
|
return unless target.homepage_res.body =~ PATTERN || target.error_404_res.body =~ PATTERN
|
||||||
|
|
||||||
Model::Theme.new(
|
Model::Theme.new(
|
||||||
Regexp.last_match[1],
|
Regexp.last_match[1],
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ module WPScan
|
|||||||
include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack
|
include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack
|
||||||
|
|
||||||
def login_request(username, password)
|
def login_request(username, password)
|
||||||
target.method_call('wp.getUsersBlogs', [username, password])
|
target.method_call('wp.getUsersBlogs', [username, password], cache_ttl: 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid_credentials?(response)
|
def valid_credentials?(response)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
target.multi_call(methods).run
|
target.multi_call(methods, cache_ttl: 0).run
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Array<Model::User> ] users
|
# @param [ Array<Model::User> ] users
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'plugins/urls_in_homepage'
|
require_relative 'plugins/urls_in_homepage'
|
||||||
|
require_relative 'plugins/urls_in_404_page'
|
||||||
require_relative 'plugins/known_locations'
|
require_relative 'plugins/known_locations'
|
||||||
# From the DynamicFinders
|
# From the DynamicFinders
|
||||||
require_relative 'plugins/comment'
|
require_relative 'plugins/comment'
|
||||||
@@ -22,6 +23,7 @@ module WPScan
|
|||||||
def initialize(target)
|
def initialize(target)
|
||||||
finders <<
|
finders <<
|
||||||
Plugins::UrlsInHomepage.new(target) <<
|
Plugins::UrlsInHomepage.new(target) <<
|
||||||
|
Plugins::UrlsIn404Page.new(target) <<
|
||||||
Plugins::HeaderPattern.new(target) <<
|
Plugins::HeaderPattern.new(target) <<
|
||||||
Plugins::Comment.new(target) <<
|
Plugins::Comment.new(target) <<
|
||||||
Plugins::Xpath.new(target) <<
|
Plugins::Xpath.new(target) <<
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, slug|
|
||||||
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
finding_opts = opts.merge(found_by: found_by,
|
||||||
|
confidence: 80,
|
||||||
|
interesting_entries: ["#{res.effective_url}, status: #{res.code}"])
|
||||||
|
|
||||||
|
found << Model::Plugin.new(slug, target, finding_opts)
|
||||||
|
|
||||||
raise Error::PluginsThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
raise Error::PluginsThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
||||||
end
|
end
|
||||||
|
|||||||
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 Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# URLs In Homepage Finder
|
# URLs In Homepage Finder
|
||||||
# Typically, the items detected from URLs like
|
# Typically, the items detected from URLs like /wp-content/plugins/<slug>/
|
||||||
# /wp-content/plugins/<slug>/
|
|
||||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||||
include WpItems::URLsInHomepage
|
include WpItems::UrlsInPage
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
#
|
#
|
||||||
@@ -21,6 +20,11 @@ module WPScan
|
|||||||
|
|
||||||
found
|
found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ Typhoeus::Response ]
|
||||||
|
def page_res
|
||||||
|
@page_res ||= target.homepage_res
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'themes/urls_in_homepage'
|
require_relative 'themes/urls_in_homepage'
|
||||||
|
require_relative 'themes/urls_in_404_page'
|
||||||
require_relative 'themes/known_locations'
|
require_relative 'themes/known_locations'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Themes
|
module Themes
|
||||||
# themes Finder
|
# Themes Finder
|
||||||
class Base
|
class Base
|
||||||
include CMSScanner::Finders::SameTypeFinder
|
include CMSScanner::Finders::SameTypeFinder
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ module WPScan
|
|||||||
def initialize(target)
|
def initialize(target)
|
||||||
finders <<
|
finders <<
|
||||||
Themes::UrlsInHomepage.new(target) <<
|
Themes::UrlsInHomepage.new(target) <<
|
||||||
|
Themes::UrlsIn404Page.new(target) <<
|
||||||
Themes::KnownLocations.new(target)
|
Themes::KnownLocations.new(target)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, slug|
|
||||||
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
finding_opts = opts.merge(found_by: found_by,
|
||||||
|
confidence: 80,
|
||||||
|
interesting_entries: ["#{res.effective_url}, status: #{res.code}"])
|
||||||
|
|
||||||
|
found << Model::Theme.new(slug, target, finding_opts)
|
||||||
|
|
||||||
raise Error::ThemesThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
raise Error::ThemesThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
||||||
end
|
end
|
||||||
|
|||||||
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
|
module Themes
|
||||||
# URLs In Homepage Finder
|
# URLs In Homepage Finder
|
||||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||||
include WpItems::URLsInHomepage
|
include WpItems::UrlsInPage
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
#
|
#
|
||||||
@@ -19,6 +19,11 @@ module WPScan
|
|||||||
|
|
||||||
found
|
found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ Typhoeus::Response ]
|
||||||
|
def page_res
|
||||||
|
@page_res ||= target.homepage_res
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -97,9 +97,12 @@ module WPScan
|
|||||||
# @return [ String, nil ]
|
# @return [ String, nil ]
|
||||||
def display_name_from_body(body)
|
def display_name_from_body(body)
|
||||||
page = Nokogiri::HTML.parse(body)
|
page = Nokogiri::HTML.parse(body)
|
||||||
|
|
||||||
# WP >= 3.0
|
# WP >= 3.0
|
||||||
page.css('h1.page-title span').each do |node|
|
page.css('h1.page-title span').each do |node|
|
||||||
return node.text.to_s
|
text = node.text.to_s.strip
|
||||||
|
|
||||||
|
return text unless text.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
# WP < 3.0
|
# WP < 3.0
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ module WPScan
|
|||||||
def user_details_from_oembed_data(oembed_data)
|
def user_details_from_oembed_data(oembed_data)
|
||||||
return unless oembed_data
|
return unless oembed_data
|
||||||
|
|
||||||
|
oembed_data = oembed_data.first if oembed_data.is_a?(Array)
|
||||||
|
|
||||||
if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z}
|
if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z}
|
||||||
details = [Regexp.last_match[1], 'Author URL', 90]
|
details = [Regexp.last_match[1], 'Author URL', 90]
|
||||||
elsif oembed_data['author_name'] && !oembed_data['author_name'].empty?
|
elsif oembed_data['author_name'] && !oembed_data['author_name'].empty?
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'wp_items/urls_in_homepage'
|
require_relative 'wp_items/urls_in_page'
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module WpItems
|
module WpItems
|
||||||
# URLs In Homepage Module to use in plugins & themes finders
|
# URLs In Homepage Module to use in plugins & themes finders
|
||||||
module URLsInHomepage
|
module UrlsInPage
|
||||||
# @param [ String ] type plugins / themes
|
# @param [ String ] type plugins / themes
|
||||||
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
|
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
|
||||||
#
|
#
|
||||||
@@ -12,10 +12,12 @@ module WPScan
|
|||||||
def items_from_links(type, uniq = true)
|
def items_from_links(type, uniq = true)
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
target.in_scope_uris(target.homepage_res) do |uri|
|
target.in_scope_uris(page_res) do |uri|
|
||||||
next unless uri.to_s =~ item_attribute_pattern(type)
|
next unless uri.to_s =~ item_attribute_pattern(type)
|
||||||
|
|
||||||
found << Regexp.last_match[1]
|
slug = Regexp.last_match[1]&.strip
|
||||||
|
|
||||||
|
found << slug unless slug&.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
uniq ? found.uniq.sort : found.sort
|
uniq ? found.uniq.sort : found.sort
|
||||||
@@ -28,7 +30,7 @@ module WPScan
|
|||||||
def items_from_codes(type, uniq = true)
|
def items_from_codes(type, uniq = true)
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
target.homepage_res.html.css('script,style').each do |tag|
|
page_res.html.xpath('//script[not(@src)]|//style[not(@src)]').each do |tag|
|
||||||
code = tag.text.to_s
|
code = tag.text.to_s
|
||||||
next if code.empty?
|
next if code.empty?
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Regexp ]
|
# @return [ Regexp ]
|
||||||
def item_attribute_pattern(type)
|
def item_attribute_pattern(type)
|
||||||
@item_attribute_pattern ||= %r{\A#{item_url_pattern(type)}([^/]+)/}i
|
@item_attribute_pattern ||= %r{#{item_url_pattern(type)}([^/]+)/}i
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ String ] type
|
# @param [ String ] type
|
||||||
@@ -59,7 +61,7 @@ module WPScan
|
|||||||
item_dir = type == 'plugins' ? target.plugins_dir : target.content_dir
|
item_dir = type == 'plugins' ? target.plugins_dir : target.content_dir
|
||||||
item_url = type == 'plugins' ? target.plugins_url : target.content_url
|
item_url = type == 'plugins' ? target.plugins_url : target.content_url
|
||||||
|
|
||||||
url = /#{item_url.gsub(/\A(?:http|https)/i, 'https?').gsub('/', '\\\\\?\/')}/i
|
url = /#{item_url.gsub(/\A(?:https?)/i, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||||
item_dir = %r{(?:#{url}|\\?\/#{item_dir.gsub('/', '\\\\\?\/')}\\?/)}i
|
item_dir = %r{(?:#{url}|\\?\/#{item_dir.gsub('/', '\\\\\?\/')}\\?/)}i
|
||||||
|
|
||||||
type == 'plugins' ? item_dir : %r{#{item_dir}#{type}\\?\/}i
|
type == 'plugins' ? item_dir : %r{#{item_dir}#{type}\\?\/}i
|
||||||
@@ -28,7 +28,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
def passive_urls_xpath
|
def passive_urls_xpath
|
||||||
'//a[contains(@href, "rdf")]/@href'
|
'//a[contains(@href, "/rdf")]/@href'
|
||||||
end
|
end
|
||||||
|
|
||||||
def aggressive_urls(_opts = {})
|
def aggressive_urls(_opts = {})
|
||||||
|
|||||||
@@ -15,9 +15,16 @@ module WPScan
|
|||||||
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||||
|
# or the local metadata db otherwise
|
||||||
|
# @return [ Hash ]
|
||||||
|
def metadata
|
||||||
|
@metadata ||= db_data.empty? ? DB::Plugin.metadata_at(slug) : db_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
def db_data
|
def db_data
|
||||||
@db_data ||= DB::Plugin.db_data(slug)
|
@db_data ||= DB::VulnApi.plugin_data(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
|
|||||||
@@ -21,9 +21,16 @@ module WPScan
|
|||||||
parse_style
|
parse_style
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||||
|
# or the local metadata db otherwise
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
|
def metadata
|
||||||
|
@metadata ||= db_data.empty? ? DB::Theme.metadata_at(slug) : db_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
def db_data
|
def db_data
|
||||||
@db_data ||= DB::Theme.db_data(slug)
|
@db_data ||= DB::VulnApi.theme_data(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
@@ -94,7 +101,7 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def parse_style_tag(body, tag)
|
def parse_style_tag(body, tag)
|
||||||
value = body[/^\s*#{Regexp.escape(tag)}:[\t ]*([^\r\n]+)/i, 1]
|
value = body[/#{Regexp.escape(tag)}:[\t ]*([^\r\n\*]+)/i, 1]
|
||||||
|
|
||||||
value && !value.strip.empty? ? value.strip : nil
|
value && !value.strip.empty? ? value.strip : nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ module WPScan
|
|||||||
|
|
||||||
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
||||||
|
|
||||||
delegate :homepage_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
|
delegate :homepage_res, :error_404_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
|
||||||
|
|
||||||
# @param [ String ] slug The plugin/theme slug
|
# @param [ String ] slug The plugin/theme slug
|
||||||
# @param [ Target ] blog The targeted blog
|
# @param [ Target ] blog The targeted blog
|
||||||
@@ -23,7 +23,7 @@ module WPScan
|
|||||||
# @option opts [ Hash ] :version_detection The options to use when looking for the version
|
# @option opts [ Hash ] :version_detection The options to use when looking for the version
|
||||||
# @option opts [ String ] :url The URL of the item
|
# @option opts [ String ] :url The URL of the item
|
||||||
def initialize(slug, blog, opts = {})
|
def initialize(slug, blog, opts = {})
|
||||||
@slug = URI.decode(slug)
|
@slug = Addressable::URI.unencode(slug)
|
||||||
@blog = blog
|
@blog = blog
|
||||||
@uri = Addressable::URI.parse(opts[:url]) if opts[:url]
|
@uri = Addressable::URI.parse(opts[:url]) if opts[:url]
|
||||||
|
|
||||||
@@ -60,18 +60,18 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def latest_version
|
def latest_version
|
||||||
@latest_version ||= db_data['latest_version'] ? Model::Version.new(db_data['latest_version']) : nil
|
@latest_version ||= metadata['latest_version'] ? Model::Version.new(metadata['latest_version']) : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Not used anywhere ATM
|
# Not used anywhere ATM
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def popular?
|
def popular?
|
||||||
@popular ||= db_data['popular']
|
@popular ||= metadata['popular'] ? true : false
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def last_updated
|
def last_updated
|
||||||
@last_updated ||= db_data['last_updated']
|
@last_updated ||= metadata['last_updated']
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
@@ -83,11 +83,6 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# URI.encode is preferered over Addressable::URI.encode as it will encode
|
|
||||||
# leading # character:
|
|
||||||
# URI.encode('#t#') => %23t%23
|
|
||||||
# Addressable::URI.encode('#t#') => #t%23
|
|
||||||
#
|
|
||||||
# @param [ String ] path Optional path to merge with the uri
|
# @param [ String ] path Optional path to merge with the uri
|
||||||
#
|
#
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
@@ -95,7 +90,7 @@ module WPScan
|
|||||||
return unless @uri
|
return unless @uri
|
||||||
return @uri.to_s unless path
|
return @uri.to_s unless path
|
||||||
|
|
||||||
@uri.join(URI.encode(path)).to_s
|
@uri.join(Addressable::URI.encode(path)).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
@@ -166,7 +161,7 @@ module WPScan
|
|||||||
# @return [ Typhoeus::Response ]
|
# @return [ Typhoeus::Response ]
|
||||||
def head_and_get(path, codes = [200], params = {})
|
def head_and_get(path, codes = [200], params = {})
|
||||||
final_path = +@path_from_blog
|
final_path = +@path_from_blog
|
||||||
final_path << URI.encode(path) unless path.nil?
|
final_path << path unless path.nil?
|
||||||
|
|
||||||
blog.head_and_get(final_path, codes, params)
|
blog.head_and_get(final_path, codes, params)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -35,9 +35,16 @@ module WPScan
|
|||||||
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||||
|
# or the local metadata db otherwise
|
||||||
|
# @return [ Hash ]
|
||||||
|
def metadata
|
||||||
|
@metadata ||= db_data.empty? ? DB::Version.metadata_at(number) : db_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
def db_data
|
def db_data
|
||||||
@db_data ||= DB::Version.db_data(number)
|
@db_data ||= DB::VulnApi.wordpress_data(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<Vulnerability> ]
|
# @return [ Array<Vulnerability> ]
|
||||||
@@ -55,12 +62,12 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def release_date
|
def release_date
|
||||||
@release_date ||= db_data['release_date'] || 'Unknown'
|
@release_date ||= metadata['release_date'] || 'Unknown'
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def status
|
def status
|
||||||
@status ||= db_data['status'] || 'Unknown'
|
@status ||= metadata['status'] || 'Unknown'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
_______________________________________________________________
|
_______________________________________________________________
|
||||||
__ _______ _____
|
__ _______ _____
|
||||||
\ \ / / __ \ / ____|
|
\ \ / / __ \ / ____|
|
||||||
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
|
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
|
||||||
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
|
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
|
||||||
\ /\ / | | ____) | (__| (_| | | | |
|
\ /\ / | | ____) | (__| (_| | | | |
|
||||||
\/ \/ |_| |_____/ \___|\__,_|_| |_|
|
\/ \/ |_| |_____/ \___|\__,_|_| |_|
|
||||||
|
|
||||||
WordPress Security Scanner by the WPScan Team
|
WordPress Security Scanner by the WPScan Team
|
||||||
Version <%= WPScan::VERSION %>
|
Version <%= WPScan::VERSION %>
|
||||||
Sponsored by Sucuri - https://sucuri.net
|
<%= ' ' * ((63 - WPScan::DB::Sponsor.text.length)/2) + WPScan::DB::Sponsor.text %>
|
||||||
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
|
@_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
|
||||||
_______________________________________________________________
|
_______________________________________________________________
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
| Detected By: <%= @item.found_by %>
|
| Found By: <%= @item.found_by %>
|
||||||
<% @item.interesting_entries.each do |entry| -%>
|
<% @item.interesting_entries.each do |entry| -%>
|
||||||
| - <%= entry %>
|
| - <%= entry %>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|||||||
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_",
|
"@_WPScan_",
|
||||||
"@ethicalhack3r",
|
"@ethicalhack3r",
|
||||||
"@erwan_lr",
|
"@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|
|
WPScan::Scan.new do |s|
|
||||||
s.controllers <<
|
s.controllers <<
|
||||||
|
WPScan::Controller::VulnApi.new <<
|
||||||
WPScan::Controller::CustomDirectories.new <<
|
WPScan::Controller::CustomDirectories.new <<
|
||||||
WPScan::Controller::InterestingFindings.new <<
|
WPScan::Controller::InterestingFindings.new <<
|
||||||
WPScan::Controller::WpVersion.new <<
|
WPScan::Controller::WpVersion.new <<
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ require 'wpscan'
|
|||||||
report = MemoryProfiler.report(top: 15) do
|
report = MemoryProfiler.report(top: 15) do
|
||||||
WPScan::Scan.new do |s|
|
WPScan::Scan.new do |s|
|
||||||
s.controllers <<
|
s.controllers <<
|
||||||
|
WPScan::Controller::VulnApi.new <<
|
||||||
WPScan::Controller::CustomDirectories.new <<
|
WPScan::Controller::CustomDirectories.new <<
|
||||||
WPScan::Controller::InterestingFindings.new <<
|
WPScan::Controller::InterestingFindings.new <<
|
||||||
WPScan::Controller::WpVersion.new <<
|
WPScan::Controller::WpVersion.new <<
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ StackProf.run(mode: :cpu, out: '/tmp/stackprof-cpu.dump', interval: 500) do
|
|||||||
# require_relative 'wpscan' doesn't work
|
# require_relative 'wpscan' doesn't work
|
||||||
WPScan::Scan.new do |s|
|
WPScan::Scan.new do |s|
|
||||||
s.controllers <<
|
s.controllers <<
|
||||||
|
WPScan::Controller::VulnApi.new <<
|
||||||
WPScan::Controller::CustomDirectories.new <<
|
WPScan::Controller::CustomDirectories.new <<
|
||||||
WPScan::Controller::InterestingFindings.new <<
|
WPScan::Controller::InterestingFindings.new <<
|
||||||
WPScan::Controller::WpVersion.new <<
|
WPScan::Controller::WpVersion.new <<
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ require 'uri'
|
|||||||
require 'time'
|
require 'time'
|
||||||
require 'readline'
|
require 'readline'
|
||||||
require 'securerandom'
|
require 'securerandom'
|
||||||
|
# Monkey Patches/Fixes/Override
|
||||||
|
require 'wpscan/typhoeus/response' # Adds a from_vuln_api? method
|
||||||
# Custom Libs
|
# Custom Libs
|
||||||
require 'wpscan/helper'
|
require 'wpscan/helper'
|
||||||
require 'wpscan/db'
|
require 'wpscan/db'
|
||||||
@@ -38,12 +39,28 @@ module WPScan
|
|||||||
APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path
|
APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path
|
||||||
DB_DIR = Pathname.new(Dir.home).join('.wpscan', 'db')
|
DB_DIR = Pathname.new(Dir.home).join('.wpscan', 'db')
|
||||||
|
|
||||||
|
Typhoeus.on_complete do |response|
|
||||||
|
next if response.cached? || !response.from_vuln_api?
|
||||||
|
|
||||||
|
self.api_requests += 1
|
||||||
|
end
|
||||||
|
|
||||||
# Override, otherwise it would be returned as 'wp_scan'
|
# Override, otherwise it would be returned as 'wp_scan'
|
||||||
#
|
#
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def self.app_name
|
def self.app_name
|
||||||
'wpscan'
|
'wpscan'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ Integer ]
|
||||||
|
def self.api_requests
|
||||||
|
@@api_requests ||= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [ Integer ] value
|
||||||
|
def self.api_requests=(value)
|
||||||
|
@@api_requests = value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require "#{WPScan::APP_DIR}/app"
|
require "#{WPScan::APP_DIR}/app"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def default_user_agent
|
def default_user_agent
|
||||||
"WPScan v#{VERSION} (https://wpscan.org/)"
|
@default_user_agent ||= "WPScan v#{VERSION} (https://wpscan.org/)"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ require_relative 'db/plugins'
|
|||||||
require_relative 'db/themes'
|
require_relative 'db/themes'
|
||||||
require_relative 'db/plugin'
|
require_relative 'db/plugin'
|
||||||
require_relative 'db/theme'
|
require_relative 'db/theme'
|
||||||
|
require_relative 'db/sponsor'
|
||||||
require_relative 'db/wp_version'
|
require_relative 'db/wp_version'
|
||||||
require_relative 'db/fingerprints'
|
require_relative 'db/fingerprints'
|
||||||
|
|
||||||
|
require_relative 'db/vuln_api'
|
||||||
|
|
||||||
require_relative 'db/dynamic_finders/base'
|
require_relative 'db/dynamic_finders/base'
|
||||||
require_relative 'db/dynamic_finders/plugin'
|
require_relative 'db/dynamic_finders/plugin'
|
||||||
require_relative 'db/dynamic_finders/theme'
|
require_relative 'db/dynamic_finders/theme'
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ module WPScan
|
|||||||
module DB
|
module DB
|
||||||
# Plugin DB
|
# Plugin DB
|
||||||
class Plugin < WpItem
|
class Plugin < WpItem
|
||||||
# @return [ String ]
|
# @return [ Hash ]
|
||||||
def self.db_file
|
def self.metadata
|
||||||
@db_file ||= DB_DIR.join('plugins.json').to_s
|
@metadata ||= super['plugins'] || {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
# WP Plugins
|
# WP Plugins
|
||||||
class Plugins < WpItems
|
class Plugins < WpItems
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def self.db
|
def self.metadata
|
||||||
Plugin.db
|
Plugin.metadata
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
16
lib/wpscan/db/sponsor.rb
Normal file
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
|
module DB
|
||||||
# Theme DB
|
# Theme DB
|
||||||
class Theme < WpItem
|
class Theme < WpItem
|
||||||
# @return [ String ]
|
# @return [ Hash ]
|
||||||
def self.db_file
|
def self.metadata
|
||||||
@db_file ||= DB_DIR.join('themes.json').to_s
|
@metadata ||= super['themes'] || {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
# WP Themes
|
# WP Themes
|
||||||
class Themes < WpItems
|
class Themes < WpItems
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def self.db
|
def self.metadata
|
||||||
Theme.db
|
Theme.metadata
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ module WPScan
|
|||||||
class Updater
|
class Updater
|
||||||
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
|
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
|
||||||
FILES = %w[
|
FILES = %w[
|
||||||
plugins.json themes.json wordpresses.json
|
metadata.json wp_fingerprints.json
|
||||||
timthumbs-v3.txt config_backups.txt db_exports.txt
|
timthumbs-v3.txt config_backups.txt db_exports.txt
|
||||||
dynamic_finders.yml wp_fingerprints.json LICENSE
|
dynamic_finders.yml LICENSE sponsor.txt
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
OLD_FILES = %w[wordpress.db user-agents.txt dynamic_finders_01.yml].freeze
|
OLD_FILES = %w[
|
||||||
|
wordpress.db user-agents.txt dynamic_finders_01.yml
|
||||||
|
wordpresses.json plugins.json themes.json
|
||||||
|
].freeze
|
||||||
|
|
||||||
attr_reader :repo_directory
|
attr_reader :repo_directory
|
||||||
|
|
||||||
|
|||||||
81
lib/wpscan/db/vuln_api.rb
Normal file
81
lib/wpscan/db/vuln_api.rb
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module DB
|
||||||
|
# WPVulnDB API
|
||||||
|
class VulnApi
|
||||||
|
NON_ERROR_CODES = [200, 401].freeze
|
||||||
|
|
||||||
|
class << self
|
||||||
|
attr_accessor :token
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Addressable::URI ]
|
||||||
|
def self.uri
|
||||||
|
@uri ||= Addressable::URI.parse('https://wpvulndb.com/api/v3/')
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [ String ] path
|
||||||
|
# @param [ Hash ] params
|
||||||
|
#
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.get(path, params = {})
|
||||||
|
return {} unless token
|
||||||
|
return {} if path.end_with?('/latest') # Remove this when api/v4 is up
|
||||||
|
|
||||||
|
res = Browser.get(uri.join(path), params.merge(request_params))
|
||||||
|
|
||||||
|
return {} if res.code == 404 # This is for API inconsistencies when dots in path
|
||||||
|
return JSON.parse(res.body) if NON_ERROR_CODES.include?(res.code)
|
||||||
|
|
||||||
|
raise Error::HTTP, res
|
||||||
|
rescue Error::HTTP => e
|
||||||
|
retries ||= 0
|
||||||
|
|
||||||
|
if (retries += 1) <= 3
|
||||||
|
sleep(1)
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
|
||||||
|
{ 'http_error' => e }
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.plugin_data(slug)
|
||||||
|
get("plugins/#{slug}")&.dig(slug) || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.theme_data(slug)
|
||||||
|
get("themes/#{slug}")&.dig(slug) || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.wordpress_data(version_number)
|
||||||
|
get("wordpresses/#{version_number.tr('.', '')}")&.dig(version_number) || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.status
|
||||||
|
json = get('status', params: { version: WPScan::VERSION }, cache_ttl: 0)
|
||||||
|
|
||||||
|
json['requests_remaining'] = 'Unlimited' if json['requests_remaining'] == -1
|
||||||
|
|
||||||
|
json
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.request_params
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Host' => uri.host, # Reset in case user provided a --vhost for the target
|
||||||
|
'Referer' => nil, # Removes referer set by the cmsscanner to the target url
|
||||||
|
'CF-Connecting-IP' => nil, # Removes in case user provided one for the target
|
||||||
|
'User-Agent' => Browser.instance.default_user_agent,
|
||||||
|
'Authorization' => "Token token=#{token}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,14 +6,19 @@ module WPScan
|
|||||||
class WpItem
|
class WpItem
|
||||||
# @param [ String ] identifier The plugin/theme slug or version number
|
# @param [ String ] identifier The plugin/theme slug or version number
|
||||||
#
|
#
|
||||||
# @return [ Hash ] The JSON data from the DB associated to the identifier
|
# @return [ Hash ] The JSON data from the metadata associated to the identifier
|
||||||
def self.db_data(identifier)
|
def self.metadata_at(identifier)
|
||||||
db[identifier] || {}
|
metadata[identifier] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def self.db
|
def self.metadata
|
||||||
@db ||= read_json_file(db_file)
|
@metadata ||= read_json_file(metadata_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def self.metadata_file
|
||||||
|
@metadata_file ||= DB_DIR.join('metadata.json').to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ module WPScan
|
|||||||
class WpItems
|
class WpItems
|
||||||
# @return [ Array<String> ] The slug of all items
|
# @return [ Array<String> ] The slug of all items
|
||||||
def self.all_slugs
|
def self.all_slugs
|
||||||
db.keys
|
metadata.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<String> ] The slug of all popular items
|
# @return [ Array<String> ] The slug of all popular items
|
||||||
def self.popular_slugs
|
def self.popular_slugs
|
||||||
db.select { |_key, item| item['popular'] == true }.keys
|
metadata.select { |_key, item| item['popular'] == true }.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<String> ] The slug of all vulnerable items
|
# @return [ Array<String> ] The slug of all vulnerable items
|
||||||
def self.vulnerable_slugs
|
def self.vulnerable_slugs
|
||||||
db.reject { |_key, item| item['vulnerabilities'].empty? }.keys
|
metadata.select { |_key, item| item['vulnerabilities'] == true }.keys
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ module WPScan
|
|||||||
module DB
|
module DB
|
||||||
# WP Version
|
# WP Version
|
||||||
class Version < WpItem
|
class Version < WpItem
|
||||||
# @return [ String ]
|
# @return [ Hash ]
|
||||||
def self.db_file
|
def self.metadata
|
||||||
@db_file ||= DB_DIR.join('wordpresses.json').to_s
|
@metadata ||= super['wordpress'] || {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ end
|
|||||||
require_relative 'errors/enumeration'
|
require_relative 'errors/enumeration'
|
||||||
require_relative 'errors/http'
|
require_relative 'errors/http'
|
||||||
require_relative 'errors/update'
|
require_relative 'errors/update'
|
||||||
|
require_relative 'errors/vuln_api'
|
||||||
require_relative 'errors/wordpress'
|
require_relative 'errors/wordpress'
|
||||||
require_relative 'errors/xmlrpc'
|
require_relative 'errors/xmlrpc'
|
||||||
|
|||||||
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 [ Typhoeus::Response ] response
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @return [ Mixed ]
|
# @return [ Mixed: nil, Object, Array ]
|
||||||
def find(_response, _opts = {})
|
def find(_response, _opts = {})
|
||||||
raise NoMethodError
|
raise NoMethodError
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
|
# @return [ Mixed ] See #find
|
||||||
def passive(opts = {})
|
def passive(opts = {})
|
||||||
return if self.class::PATH
|
return if self.class::PATH
|
||||||
|
|
||||||
find(target.homepage_res, opts)
|
homepage_result = find(target.homepage_res, opts)
|
||||||
|
|
||||||
|
if homepage_result
|
||||||
|
return homepage_result unless homepage_result.is_a?(Array) && homepage_result.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
find(target.error_404_res, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
|
# @return [ Mixed ] See #find
|
||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
return unless self.class::PATH
|
return unless self.class::PATH
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
module Version
|
||||||
# Version finder using Body Pattern method. Tipically used when the response is not
|
# Version finder using Body Pattern method. Typically used when the response is not
|
||||||
# an HTML doc and Xpath can't be used
|
# an HTML doc and Xpath can't be used
|
||||||
class BodyPattern < Finders::DynamicFinder::Version::Finder
|
class BodyPattern < Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
@@ -16,7 +16,7 @@ module WPScan
|
|||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @return [ Version ]
|
# @return [ Version ]
|
||||||
def find(response, _opts = {})
|
def find(response, _opts = {})
|
||||||
return unless response.body =~ self.class::PATTERN
|
return unless response.code != 404 && response.body =~ self.class::PATTERN
|
||||||
|
|
||||||
create_version(
|
create_version(
|
||||||
Regexp.last_match[:v],
|
Regexp.last_match[:v],
|
||||||
|
|||||||
@@ -31,9 +31,14 @@ module WPScan
|
|||||||
|
|
||||||
passive_configs.each do |slug, configs|
|
passive_configs.each do |slug, configs|
|
||||||
configs.each do |klass, config|
|
configs.each do |klass, config|
|
||||||
item = process_response(opts, target.homepage_res, slug, klass, config)
|
[target.homepage_res, target.error_404_res].each do |page_res|
|
||||||
|
item = process_response(opts, page_res, slug, klass, config)
|
||||||
|
|
||||||
found << item if item.is_a?(Model::WpItem)
|
if item.is_a?(Model::WpItem)
|
||||||
|
found << item
|
||||||
|
break # No need to check the other page if detected in the current
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# This one has been disabled from the DF.yml as it was causing FPs when a plugin had numerous
|
||||||
|
# files matching a known WP version.
|
||||||
class WpItemQueryParameter < QueryParameter
|
class WpItemQueryParameter < QueryParameter
|
||||||
def xpath
|
def xpath
|
||||||
@xpath ||=
|
@xpath ||=
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ module WPScan
|
|||||||
module WordPress
|
module WordPress
|
||||||
include CMSScanner::Target::Platform::PHP
|
include CMSScanner::Target::Platform::PHP
|
||||||
|
|
||||||
WORDPRESS_PATTERN = %r{/(?:(?:wp-content/(?:themes|(?:mu\-)?plugins|uploads))|wp-includes)/}i.freeze
|
WORDPRESS_PATTERN = %r{/(?:(?:wp-content/(?:themes|(?:mu\-)?plugins|uploads))|wp-includes)/}i.freeze
|
||||||
|
WP_JSON_OEMBED_PATTERN = %r{/wp\-json/oembed/}i.freeze
|
||||||
|
WP_ADMIN_AJAX_PATTERN = %r{\\?/wp\-admin\\?/admin\-ajax\.php}i.freeze
|
||||||
|
|
||||||
# These methods are used in the associated interesting_findings finders
|
# These methods are used in the associated interesting_findings finders
|
||||||
# to keep the boolean state of the finding rather than re-check the whole thing again
|
# to keep the boolean state of the finding rather than re-check the whole thing again
|
||||||
@@ -22,22 +24,16 @@ module WPScan
|
|||||||
|
|
||||||
# @param [ Symbol ] detection_mode
|
# @param [ Symbol ] detection_mode
|
||||||
#
|
#
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ] Whether or not the target is running WordPress
|
||||||
def wordpress?(detection_mode)
|
def wordpress?(detection_mode)
|
||||||
in_scope_uris(homepage_res) do |uri|
|
[homepage_res, error_404_res].each do |page_res|
|
||||||
return true if uri.path.match(WORDPRESS_PATTERN)
|
return true if wordpress_from_meta_comments_or_scripts?(page_res)
|
||||||
end
|
end
|
||||||
|
|
||||||
homepage_res.html.css('meta[name="generator"]').each do |node|
|
|
||||||
return true if /wordpress/i.match?(node['content'])
|
|
||||||
end
|
|
||||||
|
|
||||||
return true unless comments_from_page(/wordpress/i, homepage_res).empty?
|
|
||||||
|
|
||||||
if %i[mixed aggressive].include?(detection_mode)
|
if %i[mixed aggressive].include?(detection_mode)
|
||||||
%w[wp-admin/install.php wp-login.php].each do |path|
|
%w[wp-admin/install.php wp-login.php].each do |path|
|
||||||
in_scope_uris(Browser.get_and_follow_location(url(path))).each do |uri|
|
return true if in_scope_uris(Browser.get_and_follow_location(url(path))).any? do |uri|
|
||||||
return true if uri.path.match(WORDPRESS_PATTERN)
|
WORDPRESS_PATTERN.match?(uri.path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -45,6 +41,26 @@ module WPScan
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param [ Typhoeus::Response ] response
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def wordpress_from_meta_comments_or_scripts?(response)
|
||||||
|
in_scope_uris(response) do |uri|
|
||||||
|
return true if WORDPRESS_PATTERN.match?(uri.path) || WP_JSON_OEMBED_PATTERN.match?(uri.path)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true if response.html.css('meta[name="generator"]').any? do |node|
|
||||||
|
/wordpress/i.match?(node['content'])
|
||||||
|
end
|
||||||
|
|
||||||
|
return true unless comments_from_page(/wordpress/i, response).empty?
|
||||||
|
|
||||||
|
return true if response.html.xpath('//script[not(@src)]').any? do |node|
|
||||||
|
WP_ADMIN_AJAX_PATTERN.match?(node.text)
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
COOKIE_PATTERNS = {
|
COOKIE_PATTERNS = {
|
||||||
'vjs' => /createCookie\('vjs','(?<c_value>\d+)',\d+\);/i
|
'vjs' => /createCookie\('vjs','(?<c_value>\d+)',\d+\);/i
|
||||||
}.freeze
|
}.freeze
|
||||||
@@ -82,7 +98,7 @@ module WPScan
|
|||||||
def wordpress_hosted?
|
def wordpress_hosted?
|
||||||
return true if /\.wordpress\.com$/i.match?(uri.host)
|
return true if /\.wordpress\.com$/i.match?(uri.host)
|
||||||
|
|
||||||
unless content_dir(:passive)
|
unless content_dir
|
||||||
pattern = %r{https?://s\d\.wp\.com#{WORDPRESS_PATTERN}}i.freeze
|
pattern = %r{https?://s\d\.wp\.com#{WORDPRESS_PATTERN}}i.freeze
|
||||||
|
|
||||||
uris_from_page(homepage_res) do |uri|
|
uris_from_page(homepage_res) do |uri|
|
||||||
@@ -109,6 +125,7 @@ module WPScan
|
|||||||
Browser.instance.forge_request(
|
Browser.instance.forge_request(
|
||||||
login_url,
|
login_url,
|
||||||
method: :post,
|
method: :post,
|
||||||
|
cache_ttl: 0,
|
||||||
body: { log: username, pwd: password }
|
body: { log: username, pwd: password }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,25 +13,24 @@ module WPScan
|
|||||||
@plugins_dir = dir.chomp('/')
|
@plugins_dir = dir.chomp('/')
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Symbol ] detection_mode
|
|
||||||
# @return [ String ] The wp-content directory
|
# @return [ String ] The wp-content directory
|
||||||
def content_dir(detection_mode = :mixed)
|
def content_dir
|
||||||
unless @content_dir
|
unless @content_dir
|
||||||
# scope_url_pattern is from CMSScanner::Target
|
# scope_url_pattern is from CMSScanner::Target
|
||||||
pattern = %r{#{scope_url_pattern}([\w\s\-/]+)\\?/(?:themes|plugins|uploads|cache)\\?/}i
|
pattern = %r{#{scope_url_pattern}([\w\s\-/]+?)\\?/(?:themes|plugins|uploads|cache)\\?/}i
|
||||||
|
|
||||||
in_scope_uris(homepage_res) do |uri|
|
[homepage_res, error_404_res].each do |page_res|
|
||||||
return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
in_scope_uris(page_res, '//link/@href|//script/@src|//img/@src') do |uri|
|
||||||
|
return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks for the pattern in raw JS code, as well as @content attributes of meta tags
|
||||||
|
xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, page_res) do |match|
|
||||||
|
return @content_dir = match[1]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks for the pattern in raw JS code, as well as @content attributes of meta tags
|
return @content_dir = 'wp-content' if default_content_dir_exists?
|
||||||
xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, homepage_res) do |match|
|
|
||||||
return @content_dir = match[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
unless detection_mode == :passive
|
|
||||||
return @content_dir = 'wp-content' if default_content_dir_exists?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@content_dir
|
@content_dir
|
||||||
@@ -72,7 +71,7 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def plugin_url(slug)
|
def plugin_url(slug)
|
||||||
plugins_uri.join("#{URI.encode(slug)}/").to_s
|
plugins_uri.join("#{Addressable::URI.encode(slug)}/").to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
@@ -94,7 +93,7 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def theme_url(slug)
|
def theme_url(slug)
|
||||||
themes_uri.join("#{URI.encode(slug)}/").to_s
|
themes_uri.join("#{Addressable::URI.encode(slug)}/").to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String, False ] String of the sub_dir found, false otherwise
|
# @return [ String, False ] String of the sub_dir found, false otherwise
|
||||||
@@ -107,8 +106,10 @@ module WPScan
|
|||||||
# url_pattern is from CMSScanner::Target
|
# url_pattern is from CMSScanner::Target
|
||||||
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
|
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
|
||||||
|
|
||||||
in_scope_uris(homepage_res) do |uri|
|
[homepage_res, error_404_res].each do |page_res|
|
||||||
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
in_scope_uris(page_res) do |uri|
|
||||||
|
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@sub_dir = false
|
@sub_dir = false
|
||||||
|
|||||||
13
lib/wpscan/typhoeus/response.rb
Normal file
13
lib/wpscan/typhoeus/response.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Typhoeus
|
||||||
|
# Custom Response class
|
||||||
|
class Response
|
||||||
|
# @note: Ignores requests done to the /status endpoint of the API
|
||||||
|
#
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def from_vuln_api?
|
||||||
|
effective_url.start_with?(WPScan::DB::VulnApi.uri.to_s) && !effective_url.include?('/status')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
# Version
|
# Version
|
||||||
module WPScan
|
module WPScan
|
||||||
VERSION = '3.6.3'
|
VERSION = '3.7.8'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ describe WPScan::Controller::Core do
|
|||||||
before do
|
before do
|
||||||
expect(core).to receive(:load_server_module)
|
expect(core).to receive(:load_server_module)
|
||||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
||||||
|
expect(core.target).to receive(:wordpress_hosted?).and_return(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'calls the formatter when started and finished to update the db' do
|
it 'calls the formatter when started and finished to update the db' do
|
||||||
@@ -174,56 +175,6 @@ describe WPScan::Controller::Core do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when a redirect occurs' do
|
|
||||||
before do
|
|
||||||
stub_request(:any, target_url)
|
|
||||||
|
|
||||||
expect(core.target).to receive(:homepage_res)
|
|
||||||
.at_least(1)
|
|
||||||
.and_return(Typhoeus::Response.new(effective_url: redirection, body: ''))
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'to the wp-admin/install.php' do
|
|
||||||
let(:redirection) { "#{target_url}wp-admin/install.php" }
|
|
||||||
|
|
||||||
it 'calls the formatter with the correct parameters and exit' do
|
|
||||||
expect(core.formatter).to receive(:output)
|
|
||||||
.with('not_fully_configured', hash_including(url: redirection), 'core').ordered
|
|
||||||
|
|
||||||
# TODO: Would be cool to be able to test the exit code
|
|
||||||
expect { core.before_scan }.to raise_error(SystemExit)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'to something else' do
|
|
||||||
let(:redirection) { 'http://g.com/' }
|
|
||||||
|
|
||||||
it 'raises an error' do
|
|
||||||
expect { core.before_scan }.to raise_error(CMSScanner::Error::HTTPRedirect)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'to another path with the wp-admin/install.php in the query' do
|
|
||||||
let(:redirection) { "#{target_url}index.php?a=/wp-admin/install.php" }
|
|
||||||
|
|
||||||
context 'when wordpress' do
|
|
||||||
it 'does not raise an error' do
|
|
||||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
|
||||||
|
|
||||||
expect { core.before_scan }.to_not raise_error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when not wordpress' do
|
|
||||||
it 'raises an error' do
|
|
||||||
expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)
|
|
||||||
|
|
||||||
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when hosted on wordpress.com' do
|
context 'when hosted on wordpress.com' do
|
||||||
let(:target_url) { 'http://ex.wordpress.com' }
|
let(:target_url) { 'http://ex.wordpress.com' }
|
||||||
|
|
||||||
@@ -234,52 +185,106 @@ describe WPScan::Controller::Core do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when wordpress' do
|
context 'when not hosted on wordpress.com' do
|
||||||
before do
|
before { allow(core.target).to receive(:wordpress_hosted?).and_return(false) }
|
||||||
expect(core).to receive(:load_server_module)
|
|
||||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not raise any error' do
|
context 'when a redirect occurs' do
|
||||||
expect { core.before_scan }.to_not raise_error
|
before do
|
||||||
end
|
stub_request(:any, target_url)
|
||||||
end
|
|
||||||
|
|
||||||
context 'when not wordpress' do
|
expect(core.target).to receive(:homepage_res)
|
||||||
before do
|
.at_least(1)
|
||||||
expect(core).to receive(:load_server_module)
|
.and_return(Typhoeus::Response.new(effective_url: redirection, body: ''))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no --force' do
|
context 'to the wp-admin/install.php' do
|
||||||
before { expect(core.target).to receive(:maybe_add_cookies) }
|
let(:redirection) { "#{target_url}wp-admin/install.php" }
|
||||||
|
|
||||||
context 'when no cookies added or still not wordpress after being added' do
|
it 'calls the formatter with the correct parameters and exit' do
|
||||||
it 'raises an error' do
|
expect(core.formatter).to receive(:output)
|
||||||
expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)
|
.with('not_fully_configured', hash_including(url: redirection), 'core').ordered
|
||||||
|
|
||||||
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
|
# TODO: Would be cool to be able to test the exit code
|
||||||
|
expect { core.before_scan }.to raise_error(SystemExit)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the added cookies solved it' do
|
context 'to something else' do
|
||||||
it 'does not raise an error' do
|
let(:redirection) { 'http://g.com/' }
|
||||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false).ordered
|
|
||||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true).ordered
|
it 'raises an error' do
|
||||||
|
expect { core.before_scan }.to raise_error(CMSScanner::Error::HTTPRedirect)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'to another path with the wp-admin/install.php in the query' do
|
||||||
|
let(:redirection) { "#{target_url}index.php?a=/wp-admin/install.php" }
|
||||||
|
|
||||||
|
context 'when wordpress' do
|
||||||
|
it 'does not raise an error' do
|
||||||
|
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
||||||
|
|
||||||
|
expect { core.before_scan }.to_not raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not wordpress' do
|
||||||
|
it 'raises an error' do
|
||||||
|
expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)
|
||||||
|
|
||||||
|
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when wordpress' do
|
||||||
|
before do
|
||||||
|
expect(core).to receive(:load_server_module)
|
||||||
|
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not raise any error' do
|
||||||
|
expect { core.before_scan }.to_not raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not wordpress' do
|
||||||
|
before do
|
||||||
|
expect(core).to receive(:load_server_module)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no --force' do
|
||||||
|
before { expect(core.target).to receive(:maybe_add_cookies) }
|
||||||
|
|
||||||
|
context 'when no cookies added or still not wordpress after being added' do
|
||||||
|
it 'raises an error' do
|
||||||
|
expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)
|
||||||
|
|
||||||
|
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the added cookies solved it' do
|
||||||
|
it 'does not raise an error' do
|
||||||
|
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false).ordered
|
||||||
|
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true).ordered
|
||||||
|
|
||||||
|
expect { core.before_scan }.to_not raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when --force' do
|
||||||
|
let(:cli_args) { "#{super()} --force" }
|
||||||
|
|
||||||
|
it 'does not raise any error' do
|
||||||
|
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false)
|
||||||
|
|
||||||
expect { core.before_scan }.to_not raise_error
|
expect { core.before_scan }.to_not raise_error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when --force' do
|
|
||||||
let(:cli_args) { "#{super()} --force" }
|
|
||||||
|
|
||||||
it 'does not raise any error' do
|
|
||||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false)
|
|
||||||
|
|
||||||
expect { core.before_scan }.to_not raise_error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ describe WPScan::Controller::CustomDirectories do
|
|||||||
|
|
||||||
describe '#before_scan' do
|
describe '#before_scan' do
|
||||||
context 'when the content_dir is not found and not supplied' do
|
context 'when the content_dir is not found and not supplied' do
|
||||||
before { expect(controller.target).to receive(:content_dir).with(:mixed) }
|
before { expect(controller.target).to receive(:content_dir).and_return(nil) }
|
||||||
|
|
||||||
it 'raises an exception' do
|
it 'raises an exception' do
|
||||||
expect { controller.before_scan }.to raise_error(WPScan::Error::WpContentDirNotDetected)
|
expect { controller.before_scan }.to raise_error(WPScan::Error::WpContentDirNotDetected)
|
||||||
|
|||||||
93
spec/app/controllers/vuln_api_spec.rb
Normal file
93
spec/app/controllers/vuln_api_spec.rb
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe WPScan::Controller::VulnApi do
|
||||||
|
subject(:controller) { described_class.new }
|
||||||
|
let(:target_url) { 'http://ex.lo/' }
|
||||||
|
let(:cli_args) { "--url #{target_url}" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
WPScan::ParsedCli.options = rspec_parsed_options(cli_args)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#cli_options' do
|
||||||
|
its(:cli_options) { should_not be_empty }
|
||||||
|
its(:cli_options) { should be_a Array }
|
||||||
|
|
||||||
|
it 'contains to correct options' do
|
||||||
|
expect(controller.cli_options.map(&:to_sym)).to eq %i[api_token]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#before_scan' do
|
||||||
|
context 'when no --api-token provided' do
|
||||||
|
its(:before_scan) { should be nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when --api-token given' do
|
||||||
|
let(:cli_args) { "#{super()} --api-token token" }
|
||||||
|
|
||||||
|
context 'when the token is invalid' do
|
||||||
|
before { expect(WPScan::DB::VulnApi).to receive(:status).and_return('error' => 'HTTP Token: Access denied.') }
|
||||||
|
|
||||||
|
it 'raise an InvalidApiToken error' do
|
||||||
|
expect { controller.before_scan }.to raise_error(WPScan::Error::InvalidApiToken)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the token is valid' do
|
||||||
|
context 'when the limit has been reached' do
|
||||||
|
before do
|
||||||
|
expect(WPScan::DB::VulnApi)
|
||||||
|
.to receive(:status)
|
||||||
|
.and_return('success' => true, 'plan' => 'free', 'requests_remaining' => 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an ApiLimitReached error' do
|
||||||
|
expect { controller.before_scan }.to raise_error(WPScan::Error::ApiLimitReached)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a HTTP error, like a timeout' do
|
||||||
|
before do
|
||||||
|
expect(WPScan::DB::VulnApi)
|
||||||
|
.to receive(:status)
|
||||||
|
.and_return(
|
||||||
|
'http_error' => WPScan::Error::HTTP.new(
|
||||||
|
Typhoeus::Response.new(effective_url: 'mock-url', return_code: 28)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an HTTP error' do
|
||||||
|
expect { controller.before_scan }
|
||||||
|
.to raise_error(WPScan::Error::HTTP, 'HTTP Error: mock-url (Timeout was reached)')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the token is valid and no HTTP error' do
|
||||||
|
before do
|
||||||
|
expect(WPScan::DB::VulnApi)
|
||||||
|
.to receive(:status)
|
||||||
|
.and_return('success' => true, 'plan' => 'free', 'requests_remaining' => requests)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when limited requests' do
|
||||||
|
let(:requests) { 100 }
|
||||||
|
|
||||||
|
it 'does not raise an error' do
|
||||||
|
expect { controller.before_scan }.to_not raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when unlimited requests' do
|
||||||
|
let(:requests) { 'Unlimited' }
|
||||||
|
|
||||||
|
it 'does not raise an error' do
|
||||||
|
expect { controller.before_scan }.to_not raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -9,7 +9,7 @@ describe WPScan::Finders::DbExports::KnownLocations do
|
|||||||
|
|
||||||
describe '#potential_urls' do
|
describe '#potential_urls' do
|
||||||
before do
|
before do
|
||||||
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
|
allow(target).to receive(:sub_dir).and_return(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'replace {domain_name} by its value' do
|
it 'replace {domain_name} by its value' do
|
||||||
@@ -22,11 +22,45 @@ describe WPScan::Finders::DbExports::KnownLocations do
|
|||||||
http://ex.lo/aa/backups/db_backup.sql
|
http://ex.lo/aa/backups/db_backup.sql
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
%w[dev poc www].each do |sub_domain|
|
||||||
|
context "when #{sub_domain} sub-domain" do
|
||||||
|
let(:url) { "https://#{sub_domain}.domain.tld" }
|
||||||
|
|
||||||
|
it 'replace {domain_name} by its correct value' do
|
||||||
|
expect(finder.potential_urls(opts).keys).to include "#{url}/domain.sql"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when multi-level tlds' do
|
||||||
|
let(:url) { 'https://something.com.tr' }
|
||||||
|
|
||||||
|
it 'replace {domain_name} by its correct value' do
|
||||||
|
expect(finder.potential_urls(opts).keys).to include 'https://something.com.tr/something.sql'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when multi-level tlds and sub-domain' do
|
||||||
|
let(:url) { 'https://dev.something.com.tr' }
|
||||||
|
|
||||||
|
it 'replace {domain_name} by its correct value' do
|
||||||
|
expect(finder.potential_urls(opts).keys).to include 'https://dev.something.com.tr/something.sql'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when some weird stuff' do
|
||||||
|
let(:url) { 'https://098f6bcd4621d373cade4e832627b4f6.aa-bb-ccc-dd.domain-test.com' }
|
||||||
|
|
||||||
|
it 'replace {domain_name} by its correct value' do
|
||||||
|
expect(finder.potential_urls(opts).keys).to include "#{url}/domain-test.sql"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#aggressive' do
|
describe '#aggressive' do
|
||||||
before do
|
before do
|
||||||
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
|
allow(target).to receive(:sub_dir).and_return(false)
|
||||||
expect(target).to receive(:head_or_get_params).and_return(method: :head)
|
expect(target).to receive(:head_or_get_params).and_return(method: :head)
|
||||||
|
|
||||||
finder.potential_urls(opts).each_key do |url|
|
finder.potential_urls(opts).each_key do |url|
|
||||||
|
|||||||
11
spec/app/finders/main_theme/css_style_in_404_page_spec.rb
Normal file
11
spec/app/finders/main_theme/css_style_in_404_page_spec.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe WPScan::Finders::MainTheme::CssStyleIn404Page do
|
||||||
|
subject(:finder) { described_class.new(target) }
|
||||||
|
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
|
||||||
|
let(:url) { 'http://wp.lab/' }
|
||||||
|
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style_in_404_page') }
|
||||||
|
|
||||||
|
# This stuff is just a child class of CssStyleInHomepage (using the error_404_res rather than homepage_res)
|
||||||
|
# which already has a spec
|
||||||
|
end
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
describe WPScan::Finders::MainTheme::CssStyle do
|
describe WPScan::Finders::MainTheme::CssStyleInHomepage do
|
||||||
subject(:finder) { described_class.new(target) }
|
subject(:finder) { described_class.new(target) }
|
||||||
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
|
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
|
||||||
let(:url) { 'http://wp.lab/' }
|
let(:url) { 'http://wp.lab/' }
|
||||||
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style') }
|
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style_in_homepage') }
|
||||||
|
|
||||||
describe '#passive' do
|
describe '#passive' do
|
||||||
after do
|
after do
|
||||||
@@ -33,7 +33,7 @@ describe WPScan::Finders::MainTheme::CssStyle do
|
|||||||
@expected = WPScan::Model::Theme.new(
|
@expected = WPScan::Model::Theme.new(
|
||||||
'twentyfifteen',
|
'twentyfifteen',
|
||||||
target,
|
target,
|
||||||
found_by: 'Css Style (Passive Detection)',
|
found_by: 'Css Style In Homepage (Passive Detection)',
|
||||||
confidence: 70,
|
confidence: 70,
|
||||||
style_url: 'http://wp.lab/wp-content/themes/twentyfifteen/style.css?ver=4.1.1'
|
style_url: 'http://wp.lab/wp-content/themes/twentyfifteen/style.css?ver=4.1.1'
|
||||||
)
|
)
|
||||||
@@ -47,7 +47,7 @@ describe WPScan::Finders::MainTheme::CssStyle do
|
|||||||
@expected = WPScan::Model::Theme.new(
|
@expected = WPScan::Model::Theme.new(
|
||||||
'custom',
|
'custom',
|
||||||
target,
|
target,
|
||||||
found_by: 'Css Style (Passive Detection)',
|
found_by: 'Css Style In Homepage (Passive Detection)',
|
||||||
confidence: 70,
|
confidence: 70,
|
||||||
style_url: 'http://wp.lab/wp-content/themes/custom/style.css'
|
style_url: 'http://wp.lab/wp-content/themes/custom/style.css'
|
||||||
)
|
)
|
||||||
11
spec/app/finders/main_theme/urls_in_404_page_spec.rb.rb
Normal file
11
spec/app/finders/main_theme/urls_in_404_page_spec.rb.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe WPScan::Finders::MainTheme::UrlsIn404Page do
|
||||||
|
subject(:finder) { described_class.new(target) }
|
||||||
|
let(:target) { WPScan::Target.new(url) }
|
||||||
|
let(:url) { 'http://wp.lab/' }
|
||||||
|
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'urls_in_404_page') }
|
||||||
|
|
||||||
|
# This stuff is just a child class of URLsInHomepage (using the error_404_res rather than homepage_res)
|
||||||
|
# which already has a spec
|
||||||
|
end
|
||||||
@@ -6,7 +6,8 @@ describe WPScan::Finders::MainTheme::UrlsInHomepage do
|
|||||||
let(:url) { 'http://wp.lab/' }
|
let(:url) { 'http://wp.lab/' }
|
||||||
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'urls_in_homepage') }
|
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'urls_in_homepage') }
|
||||||
|
|
||||||
it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do
|
it_behaves_like 'App::Finders::WpItems::UrlsInPage' do
|
||||||
|
let(:page_url) { url }
|
||||||
let(:type) { 'themes' }
|
let(:type) { 'themes' }
|
||||||
let(:uniq_links) { false }
|
let(:uniq_links) { false }
|
||||||
let(:uniq_codes) { false }
|
let(:uniq_codes) { false }
|
||||||
@@ -18,6 +19,8 @@ describe WPScan::Finders::MainTheme::UrlsInHomepage do
|
|||||||
before do
|
before do
|
||||||
stub_request(:get, /.*.css/)
|
stub_request(:get, /.*.css/)
|
||||||
stub_request(:get, target.url).to_return(body: File.read(fixtures.join('found.html')))
|
stub_request(:get, target.url).to_return(body: File.read(fixtures.join('found.html')))
|
||||||
|
|
||||||
|
allow(target).to receive(:content_dir).and_return('wp-content')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the expected Themes' do
|
it 'returns the expected Themes' do
|
||||||
|
|||||||
@@ -7,32 +7,50 @@ describe WPScan::Finders::MainTheme::WooFrameworkMetaGenerator do
|
|||||||
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'woo_framework_meta_generator') }
|
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'woo_framework_meta_generator') }
|
||||||
|
|
||||||
describe '#passive' do
|
describe '#passive' do
|
||||||
after do
|
before do
|
||||||
stub_request(:get, url).to_return(body: File.read(fixtures.join(@file)))
|
stub_request(:get, url).to_return(body: File.read(fixtures.join(homepage_fixture)))
|
||||||
|
stub_request(:get, ERROR_404_URL_PATTERN).to_return(body: File.read(fixtures.join(error_404_fixture)))
|
||||||
expect(finder.passive).to eql @expected
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no Woo generator' do
|
context 'when no Woo generator' do
|
||||||
|
let(:homepage_fixture) { 'no_woo_generator.html' }
|
||||||
|
let(:error_404_fixture) { 'no_woo_generator.html' }
|
||||||
|
|
||||||
it 'returns nil' do
|
it 'returns nil' do
|
||||||
@file = 'no_woo_generator.html'
|
expect(finder.passive).to eql nil
|
||||||
@expected = nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when Woo generator' do
|
context 'when Woo generator' do
|
||||||
before do
|
before do
|
||||||
expect(target).to receive(:content_dir).at_least(1).and_return('wp-content')
|
allow(target).to receive(:content_dir).and_return('wp-content')
|
||||||
stub_request(:get, "#{url}wp-content/themes/Merchant/style.css")
|
stub_request(:get, "#{url}wp-content/themes/Merchant/style.css")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the expected theme' do
|
context 'from the homepage' do
|
||||||
@file = 'woo_generator.html'
|
let(:homepage_fixture) { 'woo_generator.html' }
|
||||||
@expected = WPScan::Model::Theme.new(
|
let(:error_404_fixture) { 'no_woo_generator.html' }
|
||||||
'Merchant', target,
|
|
||||||
found_by: 'Woo Framework Meta Generator (Passive Detection)',
|
it 'returns the expected theme' do
|
||||||
confidence: 80
|
expect(finder.passive).to eql WPScan::Model::Theme.new(
|
||||||
)
|
'Merchant', target,
|
||||||
|
found_by: 'Woo Framework Meta Generator (Passive Detection)',
|
||||||
|
confidence: 80
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'from the 404 page' do
|
||||||
|
let(:homepage_fixture) { 'no_woo_generator.html' }
|
||||||
|
let(:error_404_fixture) { 'woo_generator.html' }
|
||||||
|
|
||||||
|
it 'returns the expected theme' do
|
||||||
|
expect(finder.passive).to eql WPScan::Model::Theme.new(
|
||||||
|
'Merchant', target,
|
||||||
|
found_by: 'Woo Framework Meta Generator (Passive Detection)',
|
||||||
|
confidence: 80
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ describe WPScan::Finders::MainTheme::Base do
|
|||||||
describe '#finders' do
|
describe '#finders' do
|
||||||
it 'contains the expected finders' do
|
it 'contains the expected finders' do
|
||||||
expect(main_theme.finders.map { |f| f.class.to_s.demodulize })
|
expect(main_theme.finders.map { |f| f.class.to_s.demodulize })
|
||||||
.to eq %w[CssStyle WooFrameworkMetaGenerator UrlsInHomepage]
|
.to eq %w[CssStyleInHomepage CssStyleIn404Page WooFrameworkMetaGenerator UrlsInHomepage UrlsIn404Page]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
11
spec/app/finders/plugins/urls_in_404_page_spec.rb
Normal file
11
spec/app/finders/plugins/urls_in_404_page_spec.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe WPScan::Finders::Plugins::UrlsIn404Page do
|
||||||
|
subject(:finder) { described_class.new(target) }
|
||||||
|
let(:target) { WPScan::Target.new(url) }
|
||||||
|
let(:url) { 'https://wp.lab/' }
|
||||||
|
let(:fixtures) { FINDERS_FIXTURES.join('plugins', 'urls_in_404_page') }
|
||||||
|
|
||||||
|
# This stuff is just a child class of URLsInHomepage (using the error_404_res rather than homepage_res)
|
||||||
|
# which already has a spec
|
||||||
|
end
|
||||||
@@ -3,14 +3,17 @@
|
|||||||
describe WPScan::Finders::Plugins::UrlsInHomepage do
|
describe WPScan::Finders::Plugins::UrlsInHomepage do
|
||||||
subject(:finder) { described_class.new(target) }
|
subject(:finder) { described_class.new(target) }
|
||||||
let(:target) { WPScan::Target.new(url) }
|
let(:target) { WPScan::Target.new(url) }
|
||||||
let(:url) { 'http://wp.lab/' }
|
let(:url) { 'https://wp.lab/' }
|
||||||
let(:fixtures) { FINDERS_FIXTURES.join('plugins', 'urls_in_homepage') }
|
let(:fixtures) { FINDERS_FIXTURES.join('plugins', 'urls_in_homepage') }
|
||||||
|
|
||||||
it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do
|
before { target.scope << 'sub.lab' }
|
||||||
|
|
||||||
|
it_behaves_like 'App::Finders::WpItems::UrlsInPage' do
|
||||||
|
let(:page_url) { url }
|
||||||
let(:type) { 'plugins' }
|
let(:type) { 'plugins' }
|
||||||
let(:uniq_links) { true }
|
let(:uniq_links) { true }
|
||||||
let(:uniq_codes) { true }
|
let(:uniq_codes) { true }
|
||||||
let(:expected_from_links) { (1..4).map { |i| "dl-#{i}" } }
|
let(:expected_from_links) { (1..5).map { |i| "dl-#{i}" } }
|
||||||
let(:expected_from_codes) { (1..6).map { |i| "dc-#{i}" } }
|
let(:expected_from_codes) { (1..6).map { |i| "dc-#{i}" } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ describe WPScan::Finders::Plugins::Base do
|
|||||||
describe '#finders' do
|
describe '#finders' do
|
||||||
it 'contains the expected finders' do
|
it 'contains the expected finders' do
|
||||||
expect(plugins.finders.map { |f| f.class.to_s.demodulize })
|
expect(plugins.finders.map { |f| f.class.to_s.demodulize })
|
||||||
.to eq %w[UrlsInHomepage HeaderPattern Comment Xpath BodyPattern JavascriptVar KnownLocations]
|
.to eq %w[UrlsInHomepage UrlsIn404Page HeaderPattern Comment Xpath BodyPattern JavascriptVar KnownLocations]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
11
spec/app/finders/themes/urls_in_404_page_spec.rb
Normal file
11
spec/app/finders/themes/urls_in_404_page_spec.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe WPScan::Finders::Themes::UrlsIn404Page do
|
||||||
|
subject(:finder) { described_class.new(target) }
|
||||||
|
let(:target) { WPScan::Target.new(url) }
|
||||||
|
let(:url) { 'http://wp.lab/' }
|
||||||
|
let(:fixtures) { FINDERS_FIXTURES.join('themes', 'urls_in_404_page') }
|
||||||
|
|
||||||
|
# This stuff is just a child class of URLsInHomepage (using the error_404_res rather than homepage_res)
|
||||||
|
# which already has a spec
|
||||||
|
end
|
||||||
@@ -6,7 +6,10 @@ describe WPScan::Finders::Themes::UrlsInHomepage do
|
|||||||
let(:url) { 'http://wp.lab/' }
|
let(:url) { 'http://wp.lab/' }
|
||||||
let(:fixtures) { FINDERS_FIXTURES.join('themes', 'urls_in_homepage') }
|
let(:fixtures) { FINDERS_FIXTURES.join('themes', 'urls_in_homepage') }
|
||||||
|
|
||||||
it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do
|
# before { target.scope << 'sub.lab' }
|
||||||
|
|
||||||
|
it_behaves_like 'App::Finders::WpItems::UrlsInPage' do
|
||||||
|
let(:page_url) { url }
|
||||||
let(:type) { 'themes' }
|
let(:type) { 'themes' }
|
||||||
let(:uniq_links) { true }
|
let(:uniq_links) { true }
|
||||||
let(:uniq_codes) { true }
|
let(:uniq_codes) { true }
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ describe WPScan::Finders::Themes::Base do
|
|||||||
describe '#finders' do
|
describe '#finders' do
|
||||||
it 'contains the expected finders' do
|
it 'contains the expected finders' do
|
||||||
expect(themes.finders.map { |f| f.class.to_s.demodulize })
|
expect(themes.finders.map { |f| f.class.to_s.demodulize })
|
||||||
.to eq %w[UrlsInHomepage KnownLocations]
|
.to eq %w[UrlsInHomepage UrlsIn404Page KnownLocations]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ describe WPScan::Finders::Users::AuthorIdBruteForcing do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when no display_name' do
|
context 'when no display_name' do
|
||||||
['4.1.1', '3.0', '2.9.2'].each do |file|
|
%w[4.9-span-tag 4.1.1 3.0 2.9.2].each do |file|
|
||||||
it "returns nil for #{file}-empty.html" do
|
it "returns nil for #{file}-empty.html" do
|
||||||
body = File.read(fixtures.join("#{file}-empty.html"))
|
body = File.read(fixtures.join("#{file}-empty.html"))
|
||||||
|
|
||||||
|
|||||||
@@ -19,15 +19,17 @@ describe WPScan::Finders::Users::OembedApi do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when a JSON response' do
|
context 'when a JSON response' do
|
||||||
|
let(:body) { File.read(fixture) }
|
||||||
|
|
||||||
context 'when 404' do
|
context 'when 404' do
|
||||||
let(:body) { File.read(fixtures.join('404.json')) }
|
let(:fixture) { fixtures.join('404.json') }
|
||||||
|
|
||||||
its(:aggressive) { should eql([]) }
|
its(:aggressive) { should eql([]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when 200' do
|
context 'when 200' do
|
||||||
context 'when author_url present' do
|
context 'when author_url present' do
|
||||||
let(:body) { File.read(fixtures.join('200_author_url.json')) }
|
let(:fixture) { fixtures.join('200_author_url.json') }
|
||||||
|
|
||||||
it 'returns the expected array of users' do
|
it 'returns the expected array of users' do
|
||||||
users = finder.aggressive
|
users = finder.aggressive
|
||||||
@@ -44,7 +46,7 @@ describe WPScan::Finders::Users::OembedApi do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when author_url not present but author_name' do
|
context 'when author_url not present but author_name' do
|
||||||
let(:body) { File.read(fixtures.join('200_author_name.json')) }
|
let(:fixture) { fixtures.join('200_author_name.json') }
|
||||||
|
|
||||||
it 'returns the expected array of users' do
|
it 'returns the expected array of users' do
|
||||||
users = finder.aggressive
|
users = finder.aggressive
|
||||||
@@ -59,6 +61,12 @@ describe WPScan::Finders::Users::OembedApi do
|
|||||||
expect(user.interesting_entries).to eql ['http://wp.lab/wp-json/oembed/1.0/embed?url=http://wp.lab/&format=json']
|
expect(user.interesting_entries).to eql ['http://wp.lab/wp-json/oembed/1.0/embed?url=http://wp.lab/&format=json']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when body is an array' do
|
||||||
|
let(:fixture) { fixtures.join('array.json') }
|
||||||
|
|
||||||
|
its(:aggressive) { should eql([]) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ describe WPScan::Finders::Users::WpJsonApi do
|
|||||||
describe '#api_url' do
|
describe '#api_url' do
|
||||||
let(:fixtures) { super().join('api_url') }
|
let(:fixtures) { super().join('api_url') }
|
||||||
|
|
||||||
|
before { allow(target).to receive(:sub_dir).and_return(false) }
|
||||||
|
|
||||||
context 'when url in the homepage' do
|
context 'when url in the homepage' do
|
||||||
{
|
{
|
||||||
in_scope: 'https://wp.lab/wp-json/wp/v2/users/',
|
in_scope: 'https://wp.lab/wp-json/wp/v2/users/',
|
||||||
@@ -100,7 +102,7 @@ describe WPScan::Finders::Users::WpJsonApi do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when subdir' do
|
context 'when subdir' do
|
||||||
before { allow(target).to receive(:subdir).and_return('cms') }
|
before { allow(target).to receive(:sub_dir).and_return('cms') }
|
||||||
|
|
||||||
{
|
{
|
||||||
in_scope_subdir: 'https://wp.lab/cms/wp-json/wp/v2/users/',
|
in_scope_subdir: 'https://wp.lab/cms/wp-json/wp/v2/users/',
|
||||||
|
|||||||
@@ -81,24 +81,39 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#latest_version, #last_updated, #popular' do
|
describe '#latest_version, #last_updated, #popular' do
|
||||||
context 'when none' do
|
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
||||||
let(:slug) { 'vulnerable-not-popular' }
|
|
||||||
|
context 'when no db_data and no metadata' do
|
||||||
|
let(:slug) { 'not-known' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
its(:latest_version) { should be_nil }
|
its(:latest_version) { should be_nil }
|
||||||
its(:last_updated) { should be_nil }
|
its(:last_updated) { should be_nil }
|
||||||
its(:popular?) { should be false }
|
its(:popular?) { should be false }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when values' do
|
context 'when no db_data but metadata' do
|
||||||
let(:slug) { 'no-vulns-popular' }
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
||||||
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
||||||
its(:popular?) { should be true }
|
its(:popular?) { should be true }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when db_data' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('plugins/no-vulns-popular') }
|
||||||
|
|
||||||
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.1') }
|
||||||
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z-via-api' }
|
||||||
|
its(:popular?) { should be true }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#outdated?' do
|
describe '#outdated?' do
|
||||||
|
before { allow(plugin).to receive(:db_data).and_return({}) }
|
||||||
|
|
||||||
context 'when last_version' do
|
context 'when last_version' do
|
||||||
let(:slug) { 'no-vulns-popular' }
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
|
||||||
@@ -116,13 +131,13 @@ describe WPScan::Model::Plugin do
|
|||||||
.and_return(WPScan::Model::Version.new(version_number))
|
.and_return(WPScan::Model::Version.new(version_number))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when version < last_version' do
|
context 'when version < latest_version' do
|
||||||
let(:version_number) { '1.2' }
|
let(:version_number) { '1.2' }
|
||||||
|
|
||||||
its(:outdated?) { should eql true }
|
its(:outdated?) { should eql true }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when version >= last_version' do
|
context 'when version >= latest_version' do
|
||||||
let(:version_number) { '3.0' }
|
let(:version_number) { '3.0' }
|
||||||
|
|
||||||
its(:outdated?) { should eql false }
|
its(:outdated?) { should eql false }
|
||||||
@@ -130,7 +145,7 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no last_version' do
|
context 'when no latest_version' do
|
||||||
let(:slug) { 'vulnerable-not-popular' }
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
|
||||||
context 'when no version' do
|
context 'when no version' do
|
||||||
@@ -153,13 +168,16 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#vulnerabilities' do
|
describe '#vulnerabilities' do
|
||||||
|
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
||||||
|
|
||||||
after do
|
after do
|
||||||
expect(plugin.vulnerabilities).to eq @expected
|
expect(plugin.vulnerabilities).to eq @expected
|
||||||
expect(plugin.vulnerable?).to eql @expected.empty? ? false : true
|
expect(plugin.vulnerable?).to eql @expected.empty? ? false : true
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when plugin not in the DB' do
|
context 'when plugin not in the DB' do
|
||||||
let(:slug) { 'not-in-db' }
|
let(:slug) { 'not-in-db' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
it 'returns an empty array' do
|
it 'returns an empty array' do
|
||||||
@expected = []
|
@expected = []
|
||||||
@@ -168,7 +186,8 @@ describe WPScan::Model::Plugin do
|
|||||||
|
|
||||||
context 'when in the DB' do
|
context 'when in the DB' do
|
||||||
context 'when no vulnerabilities' do
|
context 'when no vulnerabilities' do
|
||||||
let(:slug) { 'no-vulns-popular' }
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('plugins/no-vulns-popular') }
|
||||||
|
|
||||||
it 'returns an empty array' do
|
it 'returns an empty array' do
|
||||||
@expected = []
|
@expected = []
|
||||||
@@ -176,11 +195,13 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when vulnerabilities' do
|
context 'when vulnerabilities' do
|
||||||
let(:slug) { 'vulnerable-not-popular' }
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('plugins/vulnerable-not-popular') }
|
||||||
|
|
||||||
let(:all_vulns) do
|
let(:all_vulns) do
|
||||||
[
|
[
|
||||||
WPScan::Vulnerability.new(
|
WPScan::Vulnerability.new(
|
||||||
'First Vuln',
|
'First Vuln <= 6.3.10 - LFI',
|
||||||
{ wpvulndb: '1' },
|
{ wpvulndb: '1' },
|
||||||
'LFI',
|
'LFI',
|
||||||
'6.3.10'
|
'6.3.10'
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ describe WPScan::Model::Theme do
|
|||||||
before { expect(blog).to receive(:content_dir).at_least(1).and_return('wp-content') }
|
before { expect(blog).to receive(:content_dir).at_least(1).and_return('wp-content') }
|
||||||
|
|
||||||
describe '#new' do
|
describe '#new' do
|
||||||
before do
|
before { stub_request(:get, /.*\.css\z/).to_return(body: File.read(fixture)) }
|
||||||
stub_request(:get, /.*\.css\z/)
|
|
||||||
.to_return(body: File.read(fixtures.join('style.css')))
|
let(:fixture) { fixtures.join('style.css') }
|
||||||
end
|
|
||||||
|
|
||||||
its(:url) { should eql 'http://wp.lab/wp-content/themes/spec/' }
|
its(:url) { should eql 'http://wp.lab/wp-content/themes/spec/' }
|
||||||
its(:style_url) { should eql 'http://wp.lab/wp-content/themes/spec/style.css' }
|
its(:style_url) { should eql 'http://wp.lab/wp-content/themes/spec/style.css' }
|
||||||
@@ -34,6 +33,14 @@ describe WPScan::Model::Theme do
|
|||||||
|
|
||||||
its(:style_url) { should eql opts[:style_url] }
|
its(:style_url) { should eql opts[:style_url] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when some new lines are stripped' do
|
||||||
|
let(:fixture) { fixtures.join('stripped_new_lines.css') }
|
||||||
|
|
||||||
|
its(:style_name) { should eql 'Divi' }
|
||||||
|
its(:style_uri) { should eql 'http://www.elegantthemes.com/gallery/divi/' }
|
||||||
|
its(:license_uri) { should eql 'http://www.gnu.org/licenses/gpl-2.0.html' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#version' do
|
describe '#version' do
|
||||||
@@ -86,8 +93,179 @@ describe WPScan::Model::Theme do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#latest_version, #last_updated, #popular' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, /.*\.css\z/)
|
||||||
|
allow(theme).to receive(:db_data).and_return(db_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no db_data and no metadata' do
|
||||||
|
let(:slug) { 'not-known' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
|
its(:latest_version) { should be_nil }
|
||||||
|
its(:last_updated) { should be_nil }
|
||||||
|
its(:popular?) { should be false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no db_data but metadata' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
||||||
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
||||||
|
its(:popular?) { should be true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when db_data' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('themes/no-vulns-popular') }
|
||||||
|
|
||||||
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.2') }
|
||||||
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z-via-api' }
|
||||||
|
its(:popular?) { should be true }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#outdated?' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, /.*\.css\z/)
|
||||||
|
allow(theme).to receive(:db_data).and_return({})
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when last_version' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
|
||||||
|
context 'when no version' do
|
||||||
|
before { expect(theme).to receive(:version).at_least(1).and_return(nil) }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version' do
|
||||||
|
before do
|
||||||
|
expect(theme)
|
||||||
|
.to receive(:version)
|
||||||
|
.at_least(1)
|
||||||
|
.and_return(WPScan::Model::Version.new(version_number))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version < latest_version' do
|
||||||
|
let(:version_number) { '1.2' }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version >= latest_version' do
|
||||||
|
let(:version_number) { '3.0' }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no latest_version' do
|
||||||
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
|
||||||
|
context 'when no version' do
|
||||||
|
before { expect(theme).to receive(:version).at_least(1).and_return(nil) }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version' do
|
||||||
|
before do
|
||||||
|
expect(theme)
|
||||||
|
.to receive(:version)
|
||||||
|
.at_least(1)
|
||||||
|
.and_return(WPScan::Model::Version.new('1.0'))
|
||||||
|
end
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#vulnerabilities' do
|
describe '#vulnerabilities' do
|
||||||
xit
|
before do
|
||||||
|
stub_request(:get, /.*\.css\z/)
|
||||||
|
allow(theme).to receive(:db_data).and_return(db_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
expect(theme.vulnerabilities).to eq @expected
|
||||||
|
expect(theme.vulnerable?).to eql @expected.empty? ? false : true
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when theme not in the DB' do
|
||||||
|
let(:slug) { 'not-in-db' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
|
it 'returns an empty array' do
|
||||||
|
@expected = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when in the DB' do
|
||||||
|
context 'when no vulnerabilities' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('themes/no-vulns-popular') }
|
||||||
|
|
||||||
|
it 'returns an empty array' do
|
||||||
|
@expected = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when vulnerabilities' do
|
||||||
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('themes/vulnerable-not-popular') }
|
||||||
|
|
||||||
|
let(:all_vulns) do
|
||||||
|
[
|
||||||
|
WPScan::Vulnerability.new(
|
||||||
|
'First Vuln',
|
||||||
|
{ wpvulndb: '1' },
|
||||||
|
'LFI',
|
||||||
|
'6.3.10'
|
||||||
|
),
|
||||||
|
WPScan::Vulnerability.new('No Fixed In', wpvulndb: '2')
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no theme version' do
|
||||||
|
before { expect(theme).to receive(:version).at_least(1).and_return(false) }
|
||||||
|
|
||||||
|
it 'returns all the vulnerabilities' do
|
||||||
|
@expected = all_vulns
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when theme version' do
|
||||||
|
before do
|
||||||
|
expect(theme)
|
||||||
|
.to receive(:version)
|
||||||
|
.at_least(1)
|
||||||
|
.and_return(WPScan::Model::Version.new(number))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when < to a fixed_in' do
|
||||||
|
let(:number) { '5.0' }
|
||||||
|
|
||||||
|
it 'returns it' do
|
||||||
|
@expected = all_vulns
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when >= to a fixed_in' do
|
||||||
|
let(:number) { '6.3.10' }
|
||||||
|
|
||||||
|
it 'does not return it ' do
|
||||||
|
@expected = [all_vulns.last]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#parent_theme' do
|
describe '#parent_theme' do
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ describe WPScan::Model::WpItem do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'encodes the path' do
|
it 'encodes the path' do
|
||||||
expect(wp_item.url('#t#')).to eql "#{item_url}%23t%23"
|
expect(wp_item.url('#t#')).to eql "#{item_url}#t%23"
|
||||||
expect(wp_item.url('t .txt')).to eql "#{item_url}t%20.txt"
|
expect(wp_item.url('t .txt')).to eql "#{item_url}t%20.txt"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -40,11 +40,13 @@ describe WPScan::Model::WpVersion do
|
|||||||
|
|
||||||
describe '#vulnerabilities' do
|
describe '#vulnerabilities' do
|
||||||
subject(:version) { described_class.new(number) }
|
subject(:version) { described_class.new(number) }
|
||||||
|
before { allow(version).to receive(:db_data).and_return(db_data) }
|
||||||
|
|
||||||
context 'when no vulns' do
|
context 'when no vulns' do
|
||||||
let(:number) { '4.4' }
|
let(:number) { '4.4' }
|
||||||
|
let(:db_data) { { 'vulnerabilities' => [] } }
|
||||||
|
|
||||||
its(:vulnerabilities) { should eql([]) }
|
its(:vulnerabilities) { should be_empty }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when vulnerable' do
|
context 'when vulnerable' do
|
||||||
@@ -53,13 +55,30 @@ describe WPScan::Model::WpVersion do
|
|||||||
expect(version).to be_vulnerable
|
expect(version).to be_vulnerable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:all_vulns) do
|
||||||
|
[
|
||||||
|
WPScan::Vulnerability.new(
|
||||||
|
'WP 3.8.1 - Vuln 1',
|
||||||
|
{ wpvulndb: '1' },
|
||||||
|
'SQLI'
|
||||||
|
),
|
||||||
|
WPScan::Vulnerability.new(
|
||||||
|
'WP 3.8.1 - Vuln 2',
|
||||||
|
{ url: %w[url-2 url-3], osvdb: %w[10], cve: %w[2014-0166], wpvulndb: '2' },
|
||||||
|
nil,
|
||||||
|
'3.8.2'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
context 'when a signle vuln' do
|
context 'when a signle vuln' do
|
||||||
let(:number) { '3.8' }
|
let(:number) { '3.8.1' }
|
||||||
|
let(:db_data) { vuln_api_data_for('wordpresses/38') }
|
||||||
|
|
||||||
it 'returns the expected result' do
|
it 'returns the expected result' do
|
||||||
@expected = [WPScan::Vulnerability.new(
|
@expected = [WPScan::Vulnerability.new(
|
||||||
'WP 3.8 - Vuln 1',
|
'WP 3.8 - Vuln 1',
|
||||||
{ url: %w[url-4], osvdb: %w[11], wpvulndb: '3' },
|
{ url: %w[url-4], wpvulndb: '3' },
|
||||||
'AUTHBYPASS'
|
'AUTHBYPASS'
|
||||||
)]
|
)]
|
||||||
end
|
end
|
||||||
@@ -67,6 +86,7 @@ describe WPScan::Model::WpVersion do
|
|||||||
|
|
||||||
context 'when multiple vulns' do
|
context 'when multiple vulns' do
|
||||||
let(:number) { '3.8.1' }
|
let(:number) { '3.8.1' }
|
||||||
|
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
||||||
|
|
||||||
it 'returns the expected results' do
|
it 'returns the expected results' do
|
||||||
@expected = [
|
@expected = [
|
||||||
@@ -77,7 +97,7 @@ describe WPScan::Model::WpVersion do
|
|||||||
),
|
),
|
||||||
WPScan::Vulnerability.new(
|
WPScan::Vulnerability.new(
|
||||||
'WP 3.8.1 - Vuln 2',
|
'WP 3.8.1 - Vuln 2',
|
||||||
{ url: %w[url-2 url-3], osvdb: %w[10], cve: %w[2014-0166], wpvulndb: '2' },
|
{ url: %w[url-2 url-3], cve: %w[2014-0166], wpvulndb: '2' },
|
||||||
nil,
|
nil,
|
||||||
'3.8.2'
|
'3.8.2'
|
||||||
)
|
)
|
||||||
@@ -87,27 +107,30 @@ describe WPScan::Model::WpVersion do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#release_date' do
|
describe '#metadata, #release_date, #status' do
|
||||||
subject(:version) { described_class.new('3.8.1') }
|
subject(:version) { described_class.new('3.8.1') }
|
||||||
|
|
||||||
its(:release_date) { should eql '2014-01-23' }
|
before { allow(version).to receive(:db_data).and_return(db_data) }
|
||||||
|
|
||||||
context 'when the version is not in the DB' do
|
context 'when no db_data' do
|
||||||
subject(:version) { described_class.new('3.8.2') }
|
let(:db_data) { {} }
|
||||||
|
|
||||||
its(:release_date) { should eql 'Unknown' }
|
its(:release_date) { should eql '2014-01-23' }
|
||||||
|
its(:status) { should eql 'outdated' }
|
||||||
|
|
||||||
|
context 'when the version is not in the metadata' do
|
||||||
|
subject(:version) { described_class.new('3.8.2') }
|
||||||
|
|
||||||
|
its(:release_date) { should eql 'Unknown' }
|
||||||
|
its(:status) { should eql 'Unknown' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
describe '#status' do
|
context 'when db_data' do
|
||||||
subject(:version) { described_class.new('3.8.1') }
|
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
||||||
|
|
||||||
its(:status) { should eql 'outdated' }
|
its(:release_date) { should eql '2014-01-23-via-api' }
|
||||||
|
its(:status) { should eql 'outdated-via-api' }
|
||||||
context 'when the version is not in the DB' do
|
|
||||||
subject(:version) { described_class.new('3.8.2') }
|
|
||||||
|
|
||||||
its(:release_date) { should eql 'Unknown' }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ describe 'App::Views' do
|
|||||||
# in the expected output.
|
# in the expected output.
|
||||||
%i[JSON CliNoColour].each do |formatter|
|
%i[JSON CliNoColour].each do |formatter|
|
||||||
context "when #{formatter}" do
|
context "when #{formatter}" do
|
||||||
|
it_behaves_like 'App::Views::VulnApi'
|
||||||
it_behaves_like 'App::Views::WpVersion'
|
it_behaves_like 'App::Views::WpVersion'
|
||||||
it_behaves_like 'App::Views::MainTheme'
|
it_behaves_like 'App::Views::MainTheme'
|
||||||
it_behaves_like 'App::Views::Enumeration'
|
it_behaves_like 'App::Views::Enumeration'
|
||||||
|
|||||||
36109
spec/fixtures/db/dynamic_finders.yml
vendored
36109
spec/fixtures/db/dynamic_finders.yml
vendored
File diff suppressed because it is too large
Load Diff
56
spec/fixtures/db/metadata.json
vendored
Normal file
56
spec/fixtures/db/metadata.json
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"wordpress": {
|
||||||
|
"4.0": {
|
||||||
|
"release_date": "2014-09-04",
|
||||||
|
"status": "latest"
|
||||||
|
},
|
||||||
|
"3.8.1": {
|
||||||
|
"release_date": "2014-01-23",
|
||||||
|
"status": "outdated"
|
||||||
|
},
|
||||||
|
"3.8": {
|
||||||
|
"release_date": "2013-12-12",
|
||||||
|
"status": "insecure"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"no-vulns-popular": {
|
||||||
|
"vulnerabilities": false,
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": "2.0",
|
||||||
|
"last_updated": "2015-05-16T00:00:00.000Z"
|
||||||
|
},
|
||||||
|
"vulnerable-not-popular": {
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"popular": false,
|
||||||
|
"vulnerabilities": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"themes": {
|
||||||
|
"no-vulns-popular": {
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": "2.0",
|
||||||
|
"last_updated": "2015-05-16T00:00:00.000Z",
|
||||||
|
"vulnerabilities": false
|
||||||
|
},
|
||||||
|
"vulnerable-not-popular": {
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"popular": false,
|
||||||
|
"vulnerabilities": true
|
||||||
|
},
|
||||||
|
"dignitas-themes": {
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"vulnerabilities" : true
|
||||||
|
},
|
||||||
|
"yaaburnee-themes": {
|
||||||
|
"popular": false,
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"vulnerabilities" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
spec/fixtures/db/plugins.json
vendored
25
spec/fixtures/db/plugins.json
vendored
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"no-vulns-popular": {
|
|
||||||
"vulnerabilities": [],
|
|
||||||
"popular": true,
|
|
||||||
"latest_version": "2.0",
|
|
||||||
"last_updated": "2015-05-16T00:00:00.000Z"
|
|
||||||
},
|
|
||||||
"vulnerable-not-popular": {
|
|
||||||
"latest_version": null,
|
|
||||||
"last_updated": null,
|
|
||||||
"popular": false,
|
|
||||||
"vulnerabilities" : [
|
|
||||||
{
|
|
||||||
"title" : "First Vuln",
|
|
||||||
"fixed_in" : "6.3.10",
|
|
||||||
"id" : 1,
|
|
||||||
"vuln_type": "LFI"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "No Fixed In",
|
|
||||||
"id": 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
spec/fixtures/db/sponsor.txt
vendored
Normal file
1
spec/fixtures/db/sponsor.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Sponsored By Kittens
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user