Compare commits
171 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f23d0c0157 | ||
|
|
a9a38edf24 | ||
|
|
a5534f1e49 | ||
|
|
1c6469f384 | ||
|
|
8cfdbc1196 | ||
|
|
88737ca6ea | ||
|
|
45bebc60bd | ||
|
|
4f7dec4635 | ||
|
|
98739cce5a | ||
|
|
0bfbfacc27 | ||
|
|
73cd862e83 | ||
|
|
3305e9b74f | ||
|
|
c37ec0e8d0 | ||
|
|
0b005477c1 | ||
|
|
a1467f8dac | ||
|
|
40d2c34347 | ||
|
|
528270e767 | ||
|
|
f4a04b2387 | ||
|
|
14ed6ae109 | ||
|
|
4fd43694ae | ||
|
|
552d731e6a | ||
|
|
49ac3ef528 | ||
|
|
4379313f12 | ||
|
|
3901949f36 | ||
|
|
a3d8593fed | ||
|
|
7c5baeb9c7 | ||
|
|
c692db5f85 | ||
|
|
9130196ffc | ||
|
|
dad4a65118 | ||
|
|
4c34c2feb7 | ||
|
|
23522f7775 | ||
|
|
82c61398ba | ||
|
|
02871050a6 | ||
|
|
7d3b1fea6b | ||
|
|
24917fa2a6 | ||
|
|
de3d8e4a23 | ||
|
|
1502845d65 | ||
|
|
af3f10f74e | ||
|
|
c100372b31 | ||
|
|
72d699b39a | ||
|
|
7d2b8a2a8b | ||
|
|
8729c68e22 | ||
|
|
e2d48bedd9 | ||
|
|
6b241ce9b3 | ||
|
|
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 |
@@ -14,3 +14,4 @@ Dockerfile
|
||||
*.orig
|
||||
bin/wpscan-*
|
||||
.wpscan/
|
||||
.github/
|
||||
|
||||
41
.github/workflows/build.yml
vendored
Normal file
41
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
ruby: [2.4, 2.5, 2.6, 2.7]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Ruby ${{ matrix.ruby }}
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
|
||||
- name: Install GEMs
|
||||
run: |
|
||||
gem install bundler
|
||||
bundle config force_ruby_platform true
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: rubocop
|
||||
run: |
|
||||
bundle exec rubocop
|
||||
|
||||
- name: rspec
|
||||
run: |
|
||||
bundle exec rspec
|
||||
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
40
.github/workflows/gempush.yml
vendored
Normal file
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}}
|
||||
5
.rspec
5
.rspec
@@ -1,3 +1,2 @@
|
||||
--color
|
||||
--fail-fast
|
||||
--require spec_helper
|
||||
--require spec_helper
|
||||
--color
|
||||
14
.rubocop.yml
14
.rubocop.yml
@@ -4,16 +4,10 @@ AllCops:
|
||||
Exclude:
|
||||
- '*.gemspec'
|
||||
- 'vendor/**/*'
|
||||
ClassVars:
|
||||
Enabled: false
|
||||
LineLength:
|
||||
Layout/LineLength:
|
||||
Max: 120
|
||||
Lint/UriEscapeUnescape:
|
||||
Enabled: false
|
||||
MethodLength:
|
||||
Max: 20
|
||||
Exclude:
|
||||
- 'app/controllers/enumeration/cli_options.rb'
|
||||
Metrics/AbcSize:
|
||||
Max: 25
|
||||
Metrics/BlockLength:
|
||||
@@ -25,6 +19,12 @@ Metrics/ClassLength:
|
||||
- 'app/controllers/enumeration/cli_options.rb'
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 8
|
||||
Metrics/MethodLength:
|
||||
Max: 20
|
||||
Exclude:
|
||||
- 'app/controllers/enumeration/cli_options.rb'
|
||||
Style/ClassVars:
|
||||
Enabled: false
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
Style/FormatStringToken:
|
||||
|
||||
14
.simplecov
14
.simplecov
@@ -1,4 +1,18 @@
|
||||
|
||||
if ENV['GITHUB_ACTION']
|
||||
require 'simplecov-lcov'
|
||||
|
||||
SimpleCov::Formatter::LcovFormatter.config do |c|
|
||||
c.single_report_path = 'coverage/lcov.info'
|
||||
c.report_with_single_file = true
|
||||
end
|
||||
|
||||
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
|
||||
end
|
||||
|
||||
SimpleCov.start do
|
||||
enable_coverage :branch # Only supported for Ruby >= 2.5
|
||||
|
||||
add_filter '/spec/'
|
||||
add_filter 'helper'
|
||||
end
|
||||
33
.travis.yml
33
.travis.yml
@@ -1,33 +0,0 @@
|
||||
language: ruby
|
||||
sudo: false
|
||||
cache: bundler
|
||||
rvm:
|
||||
- 2.4.1
|
||||
- 2.4.2
|
||||
- 2.4.3
|
||||
- 2.4.4
|
||||
- 2.4.5
|
||||
- 2.4.6
|
||||
- 2.5.0
|
||||
- 2.5.1
|
||||
- 2.5.2
|
||||
- 2.5.3
|
||||
- 2.5.4
|
||||
- 2.5.5
|
||||
- 2.6.0
|
||||
- 2.6.1
|
||||
- 2.6.2
|
||||
- 2.6.3
|
||||
- ruby-head
|
||||
before_install:
|
||||
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||
- gem update --system
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: ruby-head
|
||||
script:
|
||||
- bundle exec rubocop
|
||||
- bundle exec rspec
|
||||
notifications:
|
||||
email:
|
||||
- team@wpscan.org
|
||||
@@ -38,4 +38,3 @@ USER wpscan
|
||||
RUN /usr/local/bundle/bin/wpscan --update --verbose
|
||||
|
||||
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]
|
||||
CMD ["--help"]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -29,8 +29,6 @@ Example cases which do not require a commercial license, and thus fall under the
|
||||
|
||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
||||
|
||||
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
||||
|
||||
Free-use Terms and Conditions;
|
||||
|
||||
3. Redistribution
|
||||
|
||||
17
README.md
17
README.md
@@ -15,7 +15,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://badge.fury.io/rb/wpscan" target="_blank"><img src="https://badge.fury.io/rb/wpscan.svg"></a>
|
||||
<a href="https://travis-ci.org/wpscanteam/wpscan" target="_blank"><img src="https://travis-ci.org/wpscanteam/wpscan.svg?branch=master"></a>
|
||||
<a href="https://github.com/wpscanteam/wpscan/actions?query=workflow%3ABuild" target="_blank"><img src="https://github.com/wpscanteam/wpscan/workflows/Build/badge.svg"></a>
|
||||
<a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
|
||||
</p>
|
||||
|
||||
@@ -77,13 +77,19 @@ docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u1-1
|
||||
|
||||
# Usage
|
||||
|
||||
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings. If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
||||
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings.
|
||||
|
||||
If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
||||
As a result, when using the ```--enumerate``` option, don't forget to set the ```--plugins-detection``` accordingly, as its default is 'passive'.
|
||||
|
||||
For more options, open a terminal and type ```wpscan --help``` (if you built wpscan from the source, you should type the command outside of the git repo)
|
||||
|
||||
The DB is located at ~/.wpscan/db
|
||||
|
||||
## Vulnerability Database
|
||||
|
||||
The WPScan CLI tool uses the [WPVulnDB API](https://wpvulndb.com/api) to retrieve WordPress vulnerability data in real time. For WPScan to retrieve the vulnerability data an API token must be supplied via the `--api-token` option, or via a configuration file, as discussed below. An API token can be obtained by registering an account on [WPVulnDB](https://wpvulndb.com/users/sign_up). Up to 50 API requests per day are given free of charge to registered users. Once the 50 API requests are exhausted, WPScan will continue to work as normal but without any vulnerability data. Users can upgrade to paid API usage to increase their API limits within their user profile on [WPVulnDB](https://wpvulndb.com/).
|
||||
|
||||
## Load CLI options from file/s
|
||||
|
||||
WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
|
||||
@@ -124,7 +130,12 @@ cli_options:
|
||||
api_token: YOUR_API_TOKEN
|
||||
```
|
||||
|
||||
Enumerating usernames
|
||||
## Load API Token From ENV (since v3.7.10)
|
||||
|
||||
The API Token will be automatically loaded from the ENV variable `WPSCAN_API_TOKEN` if present. If the `--api-token` CLI option is also provided, the value from the CLI will be used.
|
||||
|
||||
|
||||
## Enumerating usernames
|
||||
|
||||
```shell
|
||||
wpscan --url https://target.tld/ --enumerate u
|
||||
|
||||
6
Rakefile
6
Rakefile
@@ -6,14 +6,18 @@ exec = []
|
||||
|
||||
begin
|
||||
require 'rubocop/rake_task'
|
||||
|
||||
RuboCop::RakeTask.new
|
||||
|
||||
exec << :rubocop
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
begin
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec) { |t| t.rspec_opts = %w{--tag ~slow} }
|
||||
|
||||
exec << :spec
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
@@ -18,9 +18,7 @@ module WPScan
|
||||
target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir
|
||||
target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir
|
||||
|
||||
return if target.content_dir(ParsedCli.detection_mode)
|
||||
|
||||
raise Error::WpContentDirNotDetected
|
||||
raise Error::WpContentDirNotDetected unless target.content_dir
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,10 +18,10 @@ module WPScan
|
||||
choices: {
|
||||
vp: OptBoolean.new(['--vulnerable-plugins']),
|
||||
ap: OptBoolean.new(['--all-plugins']),
|
||||
p: OptBoolean.new(['--plugins']),
|
||||
p: OptBoolean.new(['--popular-plugins']),
|
||||
vt: OptBoolean.new(['--vulnerable-themes']),
|
||||
at: OptBoolean.new(['--all-themes']),
|
||||
t: OptBoolean.new(['--themes']),
|
||||
t: OptBoolean.new(['--popular-themes']),
|
||||
tt: OptBoolean.new(['--timthumbs']),
|
||||
cb: OptBoolean.new(['--config-backups']),
|
||||
dbe: OptBoolean.new(['--db-exports']),
|
||||
@@ -69,7 +69,7 @@ module WPScan
|
||||
OptInteger.new(
|
||||
['--plugins-threshold THRESHOLD',
|
||||
'Raise an error when the number of detected plugins via known locations reaches the threshold. ' \
|
||||
'Set to 0 to ignore the threshold.'], default: 100
|
||||
'Set to 0 to ignore the threshold.'], default: 100, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
@@ -98,7 +98,7 @@ module WPScan
|
||||
OptInteger.new(
|
||||
['--themes-threshold THRESHOLD',
|
||||
'Raise an error when the number of detected themes via known locations reaches the threshold. ' \
|
||||
'Set to 0 to ignore the threshold.'], default: 20
|
||||
'Set to 0 to ignore the threshold.'], default: 20, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
@@ -56,7 +56,7 @@ module WPScan
|
||||
#
|
||||
# @return [ Boolean ] Wether or not to enumerate the plugins
|
||||
def enum_plugins?(opts)
|
||||
opts[:plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
||||
opts[:popular_plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
||||
end
|
||||
|
||||
def enum_plugins
|
||||
@@ -92,7 +92,7 @@ module WPScan
|
||||
|
||||
if opts[:enumerate][:all_plugins]
|
||||
DB::Plugins.all_slugs
|
||||
elsif opts[:enumerate][:plugins]
|
||||
elsif opts[:enumerate][:popular_plugins]
|
||||
DB::Plugins.popular_slugs
|
||||
else
|
||||
DB::Plugins.vulnerable_slugs
|
||||
@@ -103,7 +103,7 @@ module WPScan
|
||||
#
|
||||
# @return [ Boolean ] Wether or not to enumerate the themes
|
||||
def enum_themes?(opts)
|
||||
opts[:themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
||||
opts[:popular_themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
||||
end
|
||||
|
||||
def enum_themes
|
||||
@@ -139,7 +139,7 @@ module WPScan
|
||||
|
||||
if opts[:enumerate][:all_themes]
|
||||
DB::Themes.all_slugs
|
||||
elsif opts[:enumerate][:themes]
|
||||
elsif opts[:enumerate][:popular_themes]
|
||||
DB::Themes.popular_slugs
|
||||
else
|
||||
DB::Themes.vulnerable_slugs
|
||||
|
||||
@@ -4,6 +4,8 @@ module WPScan
|
||||
module Controller
|
||||
# Controller to handle the API token
|
||||
class VulnApi < CMSScanner::Controller::Base
|
||||
ENV_KEY = 'WPSCAN_API_TOKEN'
|
||||
|
||||
def cli_options
|
||||
[
|
||||
OptString.new(['--api-token TOKEN', 'The WPVulnDB API Token to display vulnerability data'])
|
||||
@@ -11,9 +13,9 @@ module WPScan
|
||||
end
|
||||
|
||||
def before_scan
|
||||
return unless ParsedCli.api_token
|
||||
return unless ParsedCli.api_token || ENV.key?(ENV_KEY)
|
||||
|
||||
DB::VulnApi.token = ParsedCli.api_token
|
||||
DB::VulnApi.token = ParsedCli.api_token || ENV[ENV_KEY]
|
||||
|
||||
api_status = DB::VulnApi.status
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ module WPScan
|
||||
module Finders
|
||||
module DbExports
|
||||
# DB Exports finder
|
||||
# See https://github.com/wpscanteam/wpscan-v3/issues/62
|
||||
class KnownLocations < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Enumerator
|
||||
|
||||
@@ -41,7 +40,7 @@ module WPScan
|
||||
# @return [ Hash ]
|
||||
def potential_urls(opts = {})
|
||||
urls = {}
|
||||
domain_name = target.uri.host[/(^[\w|-]+)/, 1]
|
||||
domain_name = PublicSuffix.domain(target.uri.host)[/(^[\w|-]+)/, 1]
|
||||
|
||||
File.open(opts[:list]).each_with_index do |path, index|
|
||||
path.gsub!('{domain_name}', domain_name)
|
||||
|
||||
@@ -16,8 +16,7 @@ module WPScan
|
||||
target.url(path),
|
||||
confidence: 70,
|
||||
found_by: DIRECT_ACCESS,
|
||||
interesting_entries: target.directory_listing_entries(path),
|
||||
references: { url: 'https://github.com/wpscanteam/wpscan/issues/422' }
|
||||
interesting_entries: target.directory_listing_entries(path)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,11 +11,7 @@ module WPScan
|
||||
|
||||
return unless target.debug_log?(path)
|
||||
|
||||
Model::DebugLog.new(
|
||||
target.url(path),
|
||||
confidence: 100, found_by: DIRECT_ACCESS,
|
||||
references: { url: 'https://codex.wordpress.org/Debugging_in_WordPress' }
|
||||
)
|
||||
Model::DebugLog.new(target.url(path), confidence: 100, found_by: DIRECT_ACCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,12 +11,7 @@ module WPScan
|
||||
|
||||
return unless /DUPLICATOR INSTALL-LOG/.match?(target.head_and_get(path).body)
|
||||
|
||||
Model::DuplicatorInstallerLog.new(
|
||||
target.url(path),
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
references: { url: 'https://www.exploit-db.com/ghdb/3981/' }
|
||||
)
|
||||
Model::DuplicatorInstallerLog.new(target.url(path), confidence: 100, found_by: DIRECT_ACCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,10 +15,7 @@ module WPScan
|
||||
Model::EmergencyPwdResetScript.new(
|
||||
target.url(path),
|
||||
confidence: /password/i.match?(res.body) ? 100 : 40,
|
||||
found_by: DIRECT_ACCESS,
|
||||
references: {
|
||||
url: 'https://codex.wordpress.org/Resetting_Your_Password#Using_the_Emergency_Password_Reset_Script'
|
||||
}
|
||||
found_by: DIRECT_ACCESS
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,8 +16,7 @@ module WPScan
|
||||
target.url(path),
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
interesting_entries: fpd_entries,
|
||||
references: { url: 'https://www.owasp.org/index.php/Full_Path_Disclosure' }
|
||||
interesting_entries: fpd_entries
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,20 +9,14 @@ module WPScan
|
||||
def passive(_opts = {})
|
||||
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
||||
|
||||
target.in_scope_uris(target.homepage_res) do |uri|
|
||||
target.in_scope_uris(target.homepage_res, '(//@href|//@src)[contains(., "mu-plugins")]') do |uri|
|
||||
next unless uri.path&.match?(pattern)
|
||||
|
||||
url = target.url('wp-content/mu-plugins/')
|
||||
|
||||
target.mu_plugins = true
|
||||
|
||||
return Model::MuPlugins.new(
|
||||
url,
|
||||
confidence: 70,
|
||||
found_by: 'URLs In Homepage (Passive Detection)',
|
||||
to_s: "This site has 'Must Use Plugins': #{url}",
|
||||
references: { url: 'http://codex.wordpress.org/Must_Use_Plugins' }
|
||||
)
|
||||
return Model::MuPlugins.new(url, confidence: 70, found_by: 'URLs In Homepage (Passive Detection)')
|
||||
end
|
||||
nil
|
||||
end
|
||||
@@ -37,13 +31,7 @@ module WPScan
|
||||
|
||||
target.mu_plugins = true
|
||||
|
||||
Model::MuPlugins.new(
|
||||
url,
|
||||
confidence: 80,
|
||||
found_by: DIRECT_ACCESS,
|
||||
to_s: "This site has 'Must Use Plugins': #{url}",
|
||||
references: { url: 'http://codex.wordpress.org/Must_Use_Plugins' }
|
||||
)
|
||||
Model::MuPlugins.new(url, confidence: 80, found_by: DIRECT_ACCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,13 +17,7 @@ module WPScan
|
||||
|
||||
target.multisite = true
|
||||
|
||||
Model::Multisite.new(
|
||||
url,
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
to_s: 'This site seems to be a multisite',
|
||||
references: { url: 'http://codex.wordpress.org/Glossary#Multisite' }
|
||||
)
|
||||
Model::Multisite.new(url, confidence: 100, found_by: DIRECT_ACCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,12 +20,7 @@ module WPScan
|
||||
|
||||
target.registration_enabled = true
|
||||
|
||||
Model::Registration.new(
|
||||
res.effective_url,
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
to_s: "Registration is enabled: #{res.effective_url}"
|
||||
)
|
||||
Model::Registration.new(res.effective_url, confidence: 100, found_by: DIRECT_ACCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,12 +13,7 @@ module WPScan
|
||||
|
||||
return unless res.code == 200 && res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
||||
|
||||
Model::TmmDbMigrate.new(
|
||||
url,
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
references: { packetstorm: 131_957 }
|
||||
)
|
||||
Model::TmmDbMigrate.new(url, confidence: 100, found_by: DIRECT_ACCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,12 +13,7 @@ module WPScan
|
||||
|
||||
url = target.url(path)
|
||||
|
||||
Model::UploadDirectoryListing.new(
|
||||
url,
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
to_s: "Upload directory has listing enabled: #{url}"
|
||||
)
|
||||
Model::UploadDirectoryListing.new(url, confidence: 100, found_by: DIRECT_ACCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,11 +14,7 @@ module WPScan
|
||||
|
||||
return unless SQL_PATTERN.match?(res.body)
|
||||
|
||||
Model::UploadSQLDump.new(
|
||||
target.url(path),
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS
|
||||
)
|
||||
Model::UploadSQLDump.new(target.url(path), confidence: 100, found_by: DIRECT_ACCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,17 +11,7 @@ module WPScan
|
||||
|
||||
return unless res.code == 200
|
||||
|
||||
Model::WPCron.new(
|
||||
wp_cron_url,
|
||||
confidence: 60,
|
||||
found_by: DIRECT_ACCESS,
|
||||
references: {
|
||||
url: [
|
||||
'https://www.iplocation.net/defend-wordpress-from-ddos',
|
||||
'https://github.com/wpscanteam/wpscan/issues/1299'
|
||||
]
|
||||
}
|
||||
)
|
||||
Model::WPCron.new(wp_cron_url, confidence: 60, found_by: DIRECT_ACCESS)
|
||||
end
|
||||
|
||||
def wp_cron_url
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'main_theme/css_style'
|
||||
require_relative 'main_theme/css_style_in_homepage'
|
||||
require_relative 'main_theme/css_style_in_404_page'
|
||||
require_relative 'main_theme/woo_framework_meta_generator'
|
||||
require_relative 'main_theme/urls_in_homepage'
|
||||
require_relative 'main_theme/urls_in_404_page'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
@@ -14,9 +16,11 @@ module WPScan
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders <<
|
||||
MainTheme::CssStyle.new(target) <<
|
||||
MainTheme::CssStyleInHomepage.new(target) <<
|
||||
MainTheme::CssStyleIn404Page.new(target) <<
|
||||
MainTheme::WooFrameworkMetaGenerator.new(target) <<
|
||||
MainTheme::UrlsInHomepage.new(target)
|
||||
MainTheme::UrlsInHomepage.new(target) <<
|
||||
MainTheme::UrlsIn404Page.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
14
app/finders/main_theme/css_style_in_404_page.rb
Normal file
14
app/finders/main_theme/css_style_in_404_page.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# From the CSS style in the 404 page
|
||||
class CssStyleIn404Page < CssStyleInHomepage
|
||||
def passive(opts = {})
|
||||
passive_from_css_href(target.error_404_res, opts) || passive_from_style_code(target.error_404_res, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,9 +3,9 @@
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# From the css style
|
||||
class CssStyle < CMSScanner::Finders::Finder
|
||||
include Finders::WpItems::URLsInHomepage
|
||||
# From the CSS style in the homepage
|
||||
class CssStyleInHomepage < CMSScanner::Finders::Finder
|
||||
include Finders::WpItems::UrlsInPage # To have the item_code_pattern method available here
|
||||
|
||||
def create_theme(slug, style_url, opts)
|
||||
Model::Theme.new(
|
||||
@@ -20,7 +20,7 @@ module WPScan
|
||||
end
|
||||
|
||||
def passive_from_css_href(res, opts)
|
||||
target.in_scope_uris(res, '//style/@src|//link/@href') do |uri|
|
||||
target.in_scope_uris(res, '//link/@href[contains(., "style.css")]') do |uri|
|
||||
next unless uri.path =~ %r{/themes/([^\/]+)/style.css\z}i
|
||||
|
||||
return create_theme(Regexp.last_match[1], uri.to_s, opts)
|
||||
15
app/finders/main_theme/urls_in_404_page.rb
Normal file
15
app/finders/main_theme/urls_in_404_page.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# URLs In 404 Page Finder
|
||||
class UrlsIn404Page < UrlsInHomepage
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.error_404_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ module WPScan
|
||||
module MainTheme
|
||||
# URLs In Homepage Finder
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
include WpItems::UrlsInPage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
@@ -21,6 +21,11 @@ module WPScan
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.homepage_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ module WPScan
|
||||
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze
|
||||
|
||||
def passive(opts = {})
|
||||
return unless target.homepage_res.body =~ PATTERN
|
||||
return unless target.homepage_res.body =~ PATTERN || target.error_404_res.body =~ PATTERN
|
||||
|
||||
Model::Theme.new(
|
||||
Regexp.last_match[1],
|
||||
|
||||
@@ -8,7 +8,7 @@ module WPScan
|
||||
include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack
|
||||
|
||||
def login_request(username, password)
|
||||
target.method_call('wp.getUsersBlogs', [username, password])
|
||||
target.method_call('wp.getUsersBlogs', [username, password], cache_ttl: 0)
|
||||
end
|
||||
|
||||
def valid_credentials?(response)
|
||||
@@ -16,7 +16,7 @@ module WPScan
|
||||
end
|
||||
|
||||
def errored_response?(response)
|
||||
response.code != 200 && response.body !~ /login_error/i
|
||||
response.code != 200 && response.body !~ /Incorrect username or password/i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,7 +19,7 @@ module WPScan
|
||||
end
|
||||
end
|
||||
|
||||
target.multi_call(methods).run
|
||||
target.multi_call(methods, cache_ttl: 0).run
|
||||
end
|
||||
|
||||
# @param [ Array<Model::User> ] users
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'plugins/urls_in_homepage'
|
||||
require_relative 'plugins/urls_in_404_page'
|
||||
require_relative 'plugins/known_locations'
|
||||
# From the DynamicFinders
|
||||
require_relative 'plugins/comment'
|
||||
@@ -22,6 +23,7 @@ module WPScan
|
||||
def initialize(target)
|
||||
finders <<
|
||||
Plugins::UrlsInHomepage.new(target) <<
|
||||
Plugins::UrlsIn404Page.new(target) <<
|
||||
Plugins::HeaderPattern.new(target) <<
|
||||
Plugins::Comment.new(target) <<
|
||||
Plugins::Xpath.new(target) <<
|
||||
|
||||
@@ -19,8 +19,12 @@ module WPScan
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, slug|
|
||||
finding_opts = opts.merge(found_by: found_by,
|
||||
confidence: 80,
|
||||
interesting_entries: ["#{res.effective_url}, status: #{res.code}"])
|
||||
|
||||
found << Model::Plugin.new(slug, target, finding_opts)
|
||||
|
||||
raise Error::PluginsThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
||||
end
|
||||
|
||||
16
app/finders/plugins/urls_in_404_page.rb
Normal file
16
app/finders/plugins/urls_in_404_page.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# URLs In 404 Page Finder
|
||||
# Typically, the items detected from URLs like /wp-content/plugins/<slug>/
|
||||
class UrlsIn404Page < UrlsInHomepage
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.error_404_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,10 +4,9 @@ module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# URLs In Homepage Finder
|
||||
# Typically, the items detected from URLs like
|
||||
# /wp-content/plugins/<slug>/
|
||||
# Typically, the items detected from URLs like /wp-content/plugins/<slug>/
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
include WpItems::UrlsInPage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
@@ -21,6 +20,11 @@ module WPScan
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.homepage_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'themes/urls_in_homepage'
|
||||
require_relative 'themes/urls_in_404_page'
|
||||
require_relative 'themes/known_locations'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Themes
|
||||
# themes Finder
|
||||
# Themes Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::SameTypeFinder
|
||||
|
||||
@@ -14,6 +15,7 @@ module WPScan
|
||||
def initialize(target)
|
||||
finders <<
|
||||
Themes::UrlsInHomepage.new(target) <<
|
||||
Themes::UrlsIn404Page.new(target) <<
|
||||
Themes::KnownLocations.new(target)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,8 +19,12 @@ module WPScan
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, slug|
|
||||
finding_opts = opts.merge(found_by: found_by,
|
||||
confidence: 80,
|
||||
interesting_entries: ["#{res.effective_url}, status: #{res.code}"])
|
||||
|
||||
found << Model::Theme.new(slug, target, finding_opts)
|
||||
|
||||
raise Error::ThemesThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
||||
end
|
||||
|
||||
15
app/finders/themes/urls_in_404_page.rb
Normal file
15
app/finders/themes/urls_in_404_page.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Themes
|
||||
# URLs In 04 Page Finder
|
||||
class UrlsIn404Page < UrlsInHomepage
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.error_404_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ module WPScan
|
||||
module Themes
|
||||
# URLs In Homepage Finder
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
include WpItems::UrlsInPage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
@@ -19,6 +19,11 @@ module WPScan
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.homepage_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -71,11 +71,13 @@ module WPScan
|
||||
return username, 'Display Name', 50 if username
|
||||
end
|
||||
|
||||
# @param [ String ] url
|
||||
# @param [ String, Addressable::URI ] uri
|
||||
#
|
||||
# @return [ String, nil ]
|
||||
def username_from_author_url(url)
|
||||
url[%r{/author/([^/\b]+)/?}i, 1]
|
||||
def username_from_author_url(uri)
|
||||
uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
|
||||
|
||||
uri.path[%r{/author/([^/\b]+)/?}i, 1]
|
||||
end
|
||||
|
||||
# @param [ Typhoeus::Response ] res
|
||||
@@ -83,12 +85,12 @@ module WPScan
|
||||
# @return [ String, nil ] The username found
|
||||
def username_from_response(res)
|
||||
# Permalink enabled
|
||||
target.in_scope_uris(res, '//link/@href|//a/@href') do |uri|
|
||||
username = username_from_author_url(uri.to_s)
|
||||
target.in_scope_uris(res, '//@href[contains(., "author/")]') do |uri|
|
||||
username = username_from_author_url(uri)
|
||||
return username if username
|
||||
end
|
||||
|
||||
# No permalink
|
||||
# No permalink, TODO Maybe use xpath to extract the classes ?
|
||||
res.body[/<body class="archive author author-([^\s]+)[ "]/i, 1]
|
||||
end
|
||||
|
||||
@@ -97,9 +99,12 @@ module WPScan
|
||||
# @return [ String, nil ]
|
||||
def display_name_from_body(body)
|
||||
page = Nokogiri::HTML.parse(body)
|
||||
|
||||
# WP >= 3.0
|
||||
page.css('h1.page-title span').each do |node|
|
||||
return node.text.to_s
|
||||
text = node.text.to_s.strip
|
||||
|
||||
return text unless text.empty?
|
||||
end
|
||||
|
||||
# WP < 3.0
|
||||
|
||||
@@ -45,7 +45,7 @@ module WPScan
|
||||
def potential_usernames(res)
|
||||
usernames = []
|
||||
|
||||
target.in_scope_uris(res, '//a/@href') do |uri, node|
|
||||
target.in_scope_uris(res, '//a/@href[contains(., "author")]') do |uri, node|
|
||||
if uri.path =~ %r{/author/([^/\b]+)/?\z}i
|
||||
usernames << [Regexp.last_match[1], 'Author Pattern', 100]
|
||||
elsif /author=[0-9]+/.match?(uri.query)
|
||||
|
||||
@@ -34,6 +34,8 @@ module WPScan
|
||||
def user_details_from_oembed_data(oembed_data)
|
||||
return unless oembed_data
|
||||
|
||||
oembed_data = oembed_data.first if oembed_data.is_a?(Array)
|
||||
|
||||
if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z}
|
||||
details = [Regexp.last_match[1], 'Author URL', 90]
|
||||
elsif oembed_data['author_name'] && !oembed_data['author_name'].empty?
|
||||
|
||||
@@ -21,7 +21,7 @@ module WPScan
|
||||
loop do
|
||||
current_page += 1
|
||||
|
||||
res = Typhoeus.get(api_url, params: { per_page: MAX_PER_PAGE, page: current_page })
|
||||
res = Browser.get(api_url, params: { per_page: MAX_PER_PAGE, page: current_page })
|
||||
|
||||
total_pages ||= res.headers['X-WP-TotalPages'].to_i
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'wp_items/urls_in_homepage'
|
||||
require_relative 'wp_items/urls_in_page'
|
||||
|
||||
@@ -4,18 +4,24 @@ module WPScan
|
||||
module Finders
|
||||
module WpItems
|
||||
# URLs In Homepage Module to use in plugins & themes finders
|
||||
module URLsInHomepage
|
||||
module UrlsInPage
|
||||
# @param [ String ] type plugins / themes
|
||||
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
|
||||
#
|
||||
# @return [Array<String> ] The plugins/themes detected in the href, src attributes of the homepage
|
||||
# @return [ Array<String> ] The plugins/themes detected in the href, src attributes of the page
|
||||
def items_from_links(type, uniq = true)
|
||||
found = []
|
||||
xpath = format(
|
||||
'(//@href|//@src|//@data-src)[contains(., "%s")]',
|
||||
type == 'plugins' ? target.plugins_dir : target.content_dir
|
||||
)
|
||||
|
||||
target.in_scope_uris(target.homepage_res) do |uri|
|
||||
target.in_scope_uris(page_res, xpath) do |uri|
|
||||
next unless uri.to_s =~ item_attribute_pattern(type)
|
||||
|
||||
found << Regexp.last_match[1]
|
||||
slug = Regexp.last_match[1]&.strip
|
||||
|
||||
found << slug unless slug&.empty?
|
||||
end
|
||||
|
||||
uniq ? found.uniq.sort : found.sort
|
||||
@@ -28,7 +34,7 @@ module WPScan
|
||||
def items_from_codes(type, uniq = true)
|
||||
found = []
|
||||
|
||||
target.homepage_res.html.css('script,style').each do |tag|
|
||||
page_res.html.xpath('//script[not(@src)]|//style[not(@src)]').each do |tag|
|
||||
code = tag.text.to_s
|
||||
next if code.empty?
|
||||
|
||||
@@ -42,7 +48,7 @@ module WPScan
|
||||
#
|
||||
# @return [ Regexp ]
|
||||
def item_attribute_pattern(type)
|
||||
@item_attribute_pattern ||= %r{\A#{item_url_pattern(type)}([^/]+)/}i
|
||||
@item_attribute_pattern ||= %r{#{item_url_pattern(type)}([^/]+)/}i
|
||||
end
|
||||
|
||||
# @param [ String ] type
|
||||
@@ -59,7 +65,7 @@ module WPScan
|
||||
item_dir = type == 'plugins' ? target.plugins_dir : target.content_dir
|
||||
item_url = type == 'plugins' ? target.plugins_url : target.content_url
|
||||
|
||||
url = /#{item_url.gsub(/\A(?:http|https)/i, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
url = /#{item_url.gsub(/\A(?:https?)/i, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
item_dir = %r{(?:#{url}|\\?\/#{item_dir.gsub('/', '\\\\\?\/')}\\?/)}i
|
||||
|
||||
type == 'plugins' ? item_dir : %r{#{item_dir}#{type}\\?\/}i
|
||||
@@ -28,7 +28,7 @@ module WPScan
|
||||
end
|
||||
|
||||
def passive_urls_xpath
|
||||
'//a[contains(@href, "rdf")]/@href'
|
||||
'//a[contains(@href, "/rdf")]/@href'
|
||||
end
|
||||
|
||||
def aggressive_urls(_opts = {})
|
||||
|
||||
@@ -8,45 +8,110 @@ module WPScan
|
||||
end
|
||||
|
||||
#
|
||||
# Empty classes for the #type to be correctly displayed (as taken from the self.class from the parent)
|
||||
# Some classes are empty for the #type to be correctly displayed (as taken from the self.class from the parent)
|
||||
#
|
||||
class BackupDB < InterestingFinding
|
||||
# @return [ Hash ]
|
||||
def references
|
||||
@references ||= { url: ['https://github.com/wpscanteam/wpscan/issues/422'] }
|
||||
end
|
||||
end
|
||||
|
||||
class DebugLog < InterestingFinding
|
||||
# @ return [ Hash ]
|
||||
def references
|
||||
@references ||= { url: ['https://codex.wordpress.org/Debugging_in_WordPress'] }
|
||||
end
|
||||
end
|
||||
|
||||
class DuplicatorInstallerLog < InterestingFinding
|
||||
# @return [ Hash ]
|
||||
def references
|
||||
@references ||= { url: ['https://www.exploit-db.com/ghdb/3981/'] }
|
||||
end
|
||||
end
|
||||
|
||||
class EmergencyPwdResetScript < InterestingFinding
|
||||
def references
|
||||
@references ||= {
|
||||
url: ['https://codex.wordpress.org/Resetting_Your_Password#Using_the_Emergency_Password_Reset_Script']
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class FullPathDisclosure < InterestingFinding
|
||||
# @return [ Hash ]
|
||||
def references
|
||||
@references ||= { url: ['https://www.owasp.org/index.php/Full_Path_Disclosure'] }
|
||||
end
|
||||
end
|
||||
|
||||
class MuPlugins < InterestingFinding
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
@to_s ||= "This site has 'Must Use Plugins': #{url}"
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def references
|
||||
@references ||= { url: ['http://codex.wordpress.org/Must_Use_Plugins'] }
|
||||
end
|
||||
end
|
||||
|
||||
class Multisite < InterestingFinding
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
@to_s ||= 'This site seems to be a multisite'
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def references
|
||||
@references ||= { url: ['http://codex.wordpress.org/Glossary#Multisite'] }
|
||||
end
|
||||
end
|
||||
|
||||
class Readme < InterestingFinding
|
||||
end
|
||||
|
||||
class Registration < InterestingFinding
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
@to_s ||= "Registration is enabled: #{url}"
|
||||
end
|
||||
end
|
||||
|
||||
class TmmDbMigrate < InterestingFinding
|
||||
# @return [ Hash ]
|
||||
def references
|
||||
@references ||= { packetstorm: [131_957] }
|
||||
end
|
||||
end
|
||||
|
||||
class UploadDirectoryListing < InterestingFinding
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
@to_s ||= "Upload directory has listing enabled: #{url}"
|
||||
end
|
||||
end
|
||||
|
||||
class UploadSQLDump < InterestingFinding
|
||||
end
|
||||
|
||||
class WPCron < InterestingFinding
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
@to_s ||= "The external WP-Cron seems to be enabled: #{url}"
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def references
|
||||
@references ||= {
|
||||
url: [
|
||||
'https://www.iplocation.net/defend-wordpress-from-ddos',
|
||||
'https://github.com/wpscanteam/wpscan/issues/1299'
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -101,7 +101,7 @@ module WPScan
|
||||
#
|
||||
# @return [ String ]
|
||||
def parse_style_tag(body, tag)
|
||||
value = body[/^\s*#{Regexp.escape(tag)}:[\t ]*([^\r\n]+)/i, 1]
|
||||
value = body[/#{Regexp.escape(tag)}:[\t ]*([^\r\n\*]+)/i, 1]
|
||||
|
||||
value && !value.strip.empty? ? value.strip : nil
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ module WPScan
|
||||
|
||||
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
||||
|
||||
delegate :homepage_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
|
||||
delegate :homepage_res, :error_404_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
|
||||
|
||||
# @param [ String ] slug The plugin/theme slug
|
||||
# @param [ Target ] blog The targeted blog
|
||||
@@ -23,7 +23,7 @@ module WPScan
|
||||
# @option opts [ Hash ] :version_detection The options to use when looking for the version
|
||||
# @option opts [ String ] :url The URL of the item
|
||||
def initialize(slug, blog, opts = {})
|
||||
@slug = URI.decode(slug)
|
||||
@slug = Addressable::URI.unencode(slug)
|
||||
@blog = blog
|
||||
@uri = Addressable::URI.parse(opts[:url]) if opts[:url]
|
||||
|
||||
@@ -83,11 +83,6 @@ module WPScan
|
||||
end
|
||||
end
|
||||
|
||||
# URI.encode is preferered over Addressable::URI.encode as it will encode
|
||||
# leading # character:
|
||||
# URI.encode('#t#') => %23t%23
|
||||
# Addressable::URI.encode('#t#') => #t%23
|
||||
#
|
||||
# @param [ String ] path Optional path to merge with the uri
|
||||
#
|
||||
# @return [ String ]
|
||||
@@ -95,7 +90,7 @@ module WPScan
|
||||
return unless @uri
|
||||
return @uri.to_s unless path
|
||||
|
||||
@uri.join(URI.encode(path)).to_s
|
||||
@uri.join(Addressable::URI.encode(path)).to_s
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
@@ -166,7 +161,7 @@ module WPScan
|
||||
# @return [ Typhoeus::Response ]
|
||||
def head_and_get(path, codes = [200], params = {})
|
||||
final_path = +@path_from_blog
|
||||
final_path << URI.encode(path) unless path.nil?
|
||||
final_path << path unless path.nil?
|
||||
|
||||
blog.head_and_get(final_path, codes, params)
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ module WPScan
|
||||
|
||||
# @return [ Hash ]
|
||||
def references
|
||||
{
|
||||
@references ||= {
|
||||
url: ['http://codex.wordpress.org/XML-RPC_Pingback_API'],
|
||||
metasploit: [
|
||||
'auxiliary/scanner/http/wordpress_ghost_scanner',
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
_______________________________________________________________
|
||||
__ _______ _____
|
||||
\ \ / / __ \ / ____|
|
||||
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
|
||||
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
|
||||
\ /\ / | | ____) | (__| (_| | | | |
|
||||
\/ \/ |_| |_____/ \___|\__,_|_| |_|
|
||||
__ _______ _____
|
||||
\ \ / / __ \ / ____|
|
||||
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
|
||||
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
|
||||
\ /\ / | | ____) | (__| (_| | | | |
|
||||
\/ \/ |_| |_____/ \___|\__,_|_| |_|
|
||||
|
||||
WordPress Security Scanner by the WPScan Team
|
||||
Version <%= WPScan::VERSION %>
|
||||
WordPress Security Scanner by the WPScan Team
|
||||
Version <%= WPScan::VERSION %>
|
||||
<%= ' ' * ((63 - WPScan::DB::Sponsor.text.length)/2) + WPScan::DB::Sponsor.text %>
|
||||
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
|
||||
@_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
|
||||
_______________________________________________________________
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
| Detected By: <%= @item.found_by %>
|
||||
| Found By: <%= @item.found_by %>
|
||||
<% @item.interesting_entries.each do |entry| -%>
|
||||
| - <%= entry %>
|
||||
<% end -%>
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
<% end -%>
|
||||
<% else -%>
|
||||
<%= warning_icon %> No WPVulnDB API Token given, as a result vulnerability data has not been output.
|
||||
<%= warning_icon %> You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/register.
|
||||
<%= warning_icon %> You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up
|
||||
<% end -%>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"@_WPScan_",
|
||||
"@ethicalhack3r",
|
||||
"@erwan_lr",
|
||||
"@_FireFart_"
|
||||
"@firefart"
|
||||
],
|
||||
"sponsor": <%= WPScan::DB::Sponsor.text.to_json %>
|
||||
},
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"requests_remaining": <%= @status['requests_remaining'].to_json %>
|
||||
<% end -%>
|
||||
<% else -%>
|
||||
"error": "No WPVulnDB API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpvulndb.com/register."
|
||||
"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 -%>
|
||||
},
|
||||
@@ -67,13 +67,13 @@ module WPScan
|
||||
# @return [ Hash ] The params for Typhoeus::Request
|
||||
# @note Those params can't be overriden by CLI options
|
||||
def request_params
|
||||
@request_params ||= {
|
||||
@request_params ||= Browser.instance.default_connect_request_params.merge(
|
||||
timeout: 600,
|
||||
connecttimeout: 300,
|
||||
accept_encoding: 'gzip, deflate',
|
||||
cache_ttl: 0,
|
||||
headers: { 'User-Agent' => Browser.instance.default_user_agent, 'Referer' => nil }
|
||||
}
|
||||
headers: { 'User-Agent' => Browser.instance.default_user_agent }
|
||||
)
|
||||
end
|
||||
|
||||
# @return [ String ] The raw file URL associated with the given filename
|
||||
@@ -85,7 +85,7 @@ module WPScan
|
||||
def remote_file_checksum(filename)
|
||||
url = "#{remote_file_url(filename)}.sha512"
|
||||
|
||||
res = Browser.get(url, request_params)
|
||||
res = Typhoeus.get(url, request_params)
|
||||
raise Error::Download, res if res.timed_out? || res.code != 200
|
||||
|
||||
res.body.chomp
|
||||
@@ -126,7 +126,7 @@ module WPScan
|
||||
file_path = local_file_path(filename)
|
||||
file_url = remote_file_url(filename)
|
||||
|
||||
res = Browser.get(file_url, request_params)
|
||||
res = Typhoeus.get(file_url, request_params)
|
||||
raise Error::Download, res if res.timed_out? || res.code != 200
|
||||
|
||||
File.open(file_path, 'wb') { |f| f.write(res.body) }
|
||||
@@ -148,7 +148,7 @@ module WPScan
|
||||
create_backup(filename)
|
||||
dl_checksum = download(filename)
|
||||
|
||||
raise "#{filename}: checksums do not match" unless dl_checksum == db_checksum
|
||||
raise Error::ChecksumsMismatch, filename unless dl_checksum == db_checksum
|
||||
|
||||
updated << filename
|
||||
rescue StandardError => e
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module DB
|
||||
# WPVulnDB API
|
||||
class VulnApi
|
||||
NON_ERROR_CODES = [200, 401, 404].freeze
|
||||
NON_ERROR_CODES = [200, 401].freeze
|
||||
|
||||
class << self
|
||||
attr_accessor :token
|
||||
@@ -21,9 +21,12 @@ module WPScan
|
||||
# @return [ Hash ]
|
||||
def self.get(path, params = {})
|
||||
return {} unless token
|
||||
return {} if path.end_with?('/latest') # Remove this when api/v4 is up
|
||||
|
||||
res = Browser.get(uri.join(path), params.merge(request_params))
|
||||
# Typhoeus.get is used rather than Browser.get to avoid merging irrelevant params from the CLI
|
||||
res = Typhoeus.get(uri.join(path), default_request_params.merge(params))
|
||||
|
||||
return {} if res.code == 404 # This is for API inconsistencies when dots in path
|
||||
return JSON.parse(res.body) if NON_ERROR_CODES.include?(res.code)
|
||||
|
||||
raise Error::HTTP, res
|
||||
@@ -63,15 +66,14 @@ module WPScan
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.request_params
|
||||
{
|
||||
# @note Those params can not be overriden by CLI options
|
||||
def self.default_request_params
|
||||
Browser.instance.default_connect_request_params.merge(
|
||||
headers: {
|
||||
'Host' => uri.host, # Reset in case user provided a --vhost for the target
|
||||
'Referer' => nil, # Removes referer set by the cmsscanner to the target url
|
||||
'User-Agent' => Browser.instance.default_user_agent,
|
||||
'Authorization' => "Token token=#{token}"
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,5 +8,17 @@ module WPScan
|
||||
'Update required, you can not run a scan if a database file is missing.'
|
||||
end
|
||||
end
|
||||
|
||||
class ChecksumsMismatch < Standard
|
||||
attr_reader :db_file
|
||||
|
||||
def initialize(db_file)
|
||||
@db_file = db_file
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{db_file}: checksums do not match. Please try again in a few minutes."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,19 +44,27 @@ module WPScan
|
||||
#
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Mixed ]
|
||||
# @return [ Mixed: nil, Object, Array ]
|
||||
def find(_response, _opts = {})
|
||||
raise NoMethodError
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Mixed ] See #find
|
||||
def passive(opts = {})
|
||||
return if self.class::PATH
|
||||
|
||||
find(target.homepage_res, opts)
|
||||
homepage_result = find(target.homepage_res, opts)
|
||||
|
||||
if homepage_result
|
||||
return homepage_result unless homepage_result.is_a?(Array) && homepage_result.empty?
|
||||
end
|
||||
|
||||
find(target.error_404_res, opts)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Mixed ] See #find
|
||||
def aggressive(opts = {})
|
||||
return unless self.class::PATH
|
||||
|
||||
|
||||
@@ -31,9 +31,14 @@ module WPScan
|
||||
|
||||
passive_configs.each do |slug, configs|
|
||||
configs.each do |klass, config|
|
||||
item = process_response(opts, target.homepage_res, slug, klass, config)
|
||||
[target.homepage_res, target.error_404_res].each do |page_res|
|
||||
item = process_response(opts, page_res, slug, klass, config)
|
||||
|
||||
found << item if item.is_a?(Model::WpItem)
|
||||
if item.is_a?(Model::WpItem)
|
||||
found << item
|
||||
break # No need to check the other page if detected in the current
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ module WPScan
|
||||
end
|
||||
end
|
||||
|
||||
# This one has been disabled from the DF.yml as it was causing FPs when a plugin had numerous
|
||||
# files matching a known WP version.
|
||||
class WpItemQueryParameter < QueryParameter
|
||||
def xpath
|
||||
@xpath ||=
|
||||
|
||||
@@ -11,7 +11,9 @@ module WPScan
|
||||
module WordPress
|
||||
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
|
||||
# to keep the boolean state of the finding rather than re-check the whole thing again
|
||||
@@ -22,22 +24,20 @@ module WPScan
|
||||
|
||||
# @param [ Symbol ] detection_mode
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
# @return [ Boolean ] Whether or not the target is running WordPress
|
||||
def wordpress?(detection_mode)
|
||||
in_scope_uris(homepage_res) do |uri|
|
||||
return true if uri.path.match(WORDPRESS_PATTERN)
|
||||
[homepage_res, error_404_res].each do |page_res|
|
||||
return true if wordpress_from_meta_comments_or_scripts?(page_res)
|
||||
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)
|
||||
%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 uri.path.match(WORDPRESS_PATTERN)
|
||||
res = Browser.get_and_follow_location(url(path))
|
||||
|
||||
next unless res.code == 200
|
||||
|
||||
in_scope_uris(res, '//link/@href|//script/@src') do |uri|
|
||||
return true if WORDPRESS_PATTERN.match?(uri.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -45,6 +45,26 @@ module WPScan
|
||||
false
|
||||
end
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @return [ Boolean ]
|
||||
def wordpress_from_meta_comments_or_scripts?(response)
|
||||
in_scope_uris(response, '//link/@href|//script/@src') do |uri|
|
||||
return true if WORDPRESS_PATTERN.match?(uri.path) || WP_JSON_OEMBED_PATTERN.match?(uri.path)
|
||||
end
|
||||
|
||||
return true if response.html.css('meta[name="generator"]').any? do |node|
|
||||
/wordpress/i.match?(node['content'])
|
||||
end
|
||||
|
||||
return true unless comments_from_page(/wordpress/i, response).empty?
|
||||
|
||||
return true if response.html.xpath('//script[not(@src)]').any? do |node|
|
||||
WP_ADMIN_AJAX_PATTERN.match?(node.text)
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
COOKIE_PATTERNS = {
|
||||
'vjs' => /createCookie\('vjs','(?<c_value>\d+)',\d+\);/i
|
||||
}.freeze
|
||||
@@ -82,10 +102,11 @@ module WPScan
|
||||
def wordpress_hosted?
|
||||
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
|
||||
xpath = '(//@href|//@src)[contains(., "wp.com")]'
|
||||
|
||||
uris_from_page(homepage_res) do |uri|
|
||||
uris_from_page(homepage_res, xpath) do |uri|
|
||||
return true if uri.to_s.match?(pattern)
|
||||
end
|
||||
end
|
||||
@@ -109,6 +130,7 @@ module WPScan
|
||||
Browser.instance.forge_request(
|
||||
login_url,
|
||||
method: :post,
|
||||
cache_ttl: 0,
|
||||
body: { log: username, pwd: password }
|
||||
)
|
||||
end
|
||||
|
||||
@@ -13,25 +13,24 @@ module WPScan
|
||||
@plugins_dir = dir.chomp('/')
|
||||
end
|
||||
|
||||
# @param [ Symbol ] detection_mode
|
||||
# @return [ String ] The wp-content directory
|
||||
def content_dir(detection_mode = :mixed)
|
||||
def content_dir
|
||||
unless @content_dir
|
||||
# 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|
|
||||
return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||
[homepage_res, error_404_res].each do |page_res|
|
||||
in_scope_uris(page_res, '//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
|
||||
|
||||
# Checks for the pattern in raw JS code, as well as @content attributes of meta tags
|
||||
xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, homepage_res) do |match|
|
||||
return @content_dir = match[1]
|
||||
end
|
||||
|
||||
unless detection_mode == :passive
|
||||
return @content_dir = 'wp-content' if default_content_dir_exists?
|
||||
end
|
||||
return @content_dir = 'wp-content' if default_content_dir_exists?
|
||||
end
|
||||
|
||||
@content_dir
|
||||
@@ -72,7 +71,7 @@ module WPScan
|
||||
#
|
||||
# @return [ String ]
|
||||
def plugin_url(slug)
|
||||
plugins_uri.join("#{URI.encode(slug)}/").to_s
|
||||
plugins_uri.join("#{Addressable::URI.encode(slug)}/").to_s
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
@@ -94,7 +93,7 @@ module WPScan
|
||||
#
|
||||
# @return [ String ]
|
||||
def theme_url(slug)
|
||||
themes_uri.join("#{URI.encode(slug)}/").to_s
|
||||
themes_uri.join("#{Addressable::URI.encode(slug)}/").to_s
|
||||
end
|
||||
|
||||
# @return [ String, False ] String of the sub_dir found, false otherwise
|
||||
@@ -106,9 +105,12 @@ module WPScan
|
||||
|
||||
# url_pattern is from CMSScanner::Target
|
||||
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
|
||||
xpath = '(//@src|//@href|//@data-src)[contains(., "xmlrpc.php") or contains(., "wp-includes/")]'
|
||||
|
||||
in_scope_uris(homepage_res) do |uri|
|
||||
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||
[homepage_res, error_404_res].each do |page_res|
|
||||
in_scope_uris(page_res, xpath) do |uri|
|
||||
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||
end
|
||||
end
|
||||
|
||||
@sub_dir = false
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
# Version
|
||||
module WPScan
|
||||
VERSION = '3.7.0'
|
||||
VERSION = '3.7.11'
|
||||
end
|
||||
|
||||
@@ -166,6 +166,7 @@ describe WPScan::Controller::Core do
|
||||
before do
|
||||
expect(core).to receive(:load_server_module)
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
||||
expect(core.target).to receive(:wordpress_hosted?).and_return(false)
|
||||
end
|
||||
|
||||
it 'calls the formatter when started and finished to update the db' do
|
||||
@@ -174,56 +175,6 @@ describe WPScan::Controller::Core do
|
||||
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
|
||||
let(:target_url) { 'http://ex.wordpress.com' }
|
||||
|
||||
@@ -234,52 +185,106 @@ describe WPScan::Controller::Core do
|
||||
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
|
||||
context 'when not hosted on wordpress.com' do
|
||||
before { allow(core.target).to receive(:wordpress_hosted?).and_return(false) }
|
||||
|
||||
it 'does not raise any error' do
|
||||
expect { core.before_scan }.to_not raise_error
|
||||
end
|
||||
end
|
||||
context 'when a redirect occurs' do
|
||||
before do
|
||||
stub_request(:any, target_url)
|
||||
|
||||
context 'when not wordpress' do
|
||||
before do
|
||||
expect(core).to receive(:load_server_module)
|
||||
end
|
||||
expect(core.target).to receive(:homepage_res)
|
||||
.at_least(1)
|
||||
.and_return(Typhoeus::Response.new(effective_url: redirection, body: ''))
|
||||
end
|
||||
|
||||
context 'when no --force' do
|
||||
before { expect(core.target).to receive(:maybe_add_cookies) }
|
||||
context 'to the wp-admin/install.php' do
|
||||
let(:redirection) { "#{target_url}wp-admin/install.php" }
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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 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
|
||||
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
|
||||
|
||||
@@ -20,7 +20,7 @@ describe WPScan::Controller::CustomDirectories do
|
||||
|
||||
describe '#before_scan' 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
|
||||
expect { controller.before_scan }.to raise_error(WPScan::Error::WpContentDirNotDetected)
|
||||
|
||||
@@ -74,20 +74,40 @@ describe WPScan::Controller::VulnApi do
|
||||
context 'when limited requests' do
|
||||
let(:requests) { 100 }
|
||||
|
||||
it 'does not raise an error' do
|
||||
it 'sets the token and does not raise an error' do
|
||||
expect { controller.before_scan }.to_not raise_error
|
||||
|
||||
expect(WPScan::DB::VulnApi.token).to eql 'token'
|
||||
end
|
||||
|
||||
context 'when unlimited requests' do
|
||||
let(:requests) { 'Unlimited' }
|
||||
|
||||
it 'does not raise an error' do
|
||||
it 'sets the token and does not raise an error' do
|
||||
expect { controller.before_scan }.to_not raise_error
|
||||
|
||||
expect(WPScan::DB::VulnApi.token).to eql 'token'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when token in ENV' do
|
||||
before do
|
||||
ENV[described_class::ENV_KEY] = 'token-from-env'
|
||||
|
||||
expect(WPScan::DB::VulnApi)
|
||||
.to receive(:status)
|
||||
.and_return('success' => true, 'plan' => 'free', 'requests_remaining' => 'Unlimited')
|
||||
end
|
||||
|
||||
it 'sets the token and does not raise an error' do
|
||||
expect { controller.before_scan }.to_not raise_error
|
||||
|
||||
expect(WPScan::DB::VulnApi.token).to eql 'token-from-env'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,7 @@ describe WPScan::Finders::DbExports::KnownLocations do
|
||||
|
||||
describe '#potential_urls' do
|
||||
before do
|
||||
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
|
||||
allow(target).to receive(:sub_dir).and_return(false)
|
||||
end
|
||||
|
||||
it 'replace {domain_name} by its value' do
|
||||
@@ -22,11 +22,45 @@ describe WPScan::Finders::DbExports::KnownLocations do
|
||||
http://ex.lo/aa/backups/db_backup.sql
|
||||
]
|
||||
end
|
||||
|
||||
%w[dev poc www].each do |sub_domain|
|
||||
context "when #{sub_domain} sub-domain" do
|
||||
let(:url) { "https://#{sub_domain}.domain.tld" }
|
||||
|
||||
it '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
|
||||
|
||||
describe '#aggressive' do
|
||||
before do
|
||||
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
|
||||
allow(target).to receive(:sub_dir).and_return(false)
|
||||
expect(target).to receive(:head_or_get_params).and_return(method: :head)
|
||||
|
||||
finder.potential_urls(opts).each_key do |url|
|
||||
|
||||
@@ -6,8 +6,55 @@ describe WPScan::Finders::InterestingFindings::MuPlugins do
|
||||
let(:url) { 'http://ex.lo/' }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('interesting_findings', 'mu_plugins') }
|
||||
|
||||
before do
|
||||
expect(target).to receive(:content_dir).at_least(1).and_return('wp-content')
|
||||
end
|
||||
|
||||
describe '#passive' do
|
||||
xit
|
||||
before { stub_request(:get, url).to_return(body: body) }
|
||||
|
||||
context 'when no uris' do
|
||||
let(:body) { '' }
|
||||
|
||||
its(:passive) { should be nil }
|
||||
end
|
||||
|
||||
context 'when a large amount of unrelated uris' do
|
||||
let(:body) do
|
||||
Array.new(250) { |i| "<a href='#{url}#{i}.html'>Some Link</a><img src='#{url}img-#{i}.png'/>" }.join("\n")
|
||||
end
|
||||
|
||||
it 'should not take a while to process the page' do
|
||||
time_start = Time.now
|
||||
result = finder.passive
|
||||
time_end = Time.now
|
||||
|
||||
expect(result).to be nil
|
||||
expect(time_end - time_start).to be < 1
|
||||
end
|
||||
end
|
||||
|
||||
context 'when uris' do
|
||||
let(:body) { File.read(fixtures.join(fixture)) }
|
||||
|
||||
context 'when none matching' do
|
||||
let(:fixture) { 'no_match.html' }
|
||||
|
||||
its(:passive) { should be nil }
|
||||
end
|
||||
|
||||
context 'when matching via href' do
|
||||
let(:fixture) { 'match_href.html' }
|
||||
|
||||
its(:passive) { should be_a WPScan::Model::MuPlugins }
|
||||
end
|
||||
|
||||
context 'when matching from src' do
|
||||
let(:fixture) { 'match_src.html' }
|
||||
|
||||
its(:passive) { should be_a WPScan::Model::MuPlugins }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#aggressive' do
|
||||
|
||||
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
|
||||
|
||||
describe WPScan::Finders::MainTheme::CssStyle do
|
||||
describe WPScan::Finders::MainTheme::CssStyleInHomepage 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') }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style_in_homepage') }
|
||||
|
||||
describe '#passive' do
|
||||
after do
|
||||
@@ -33,7 +33,7 @@ describe WPScan::Finders::MainTheme::CssStyle do
|
||||
@expected = WPScan::Model::Theme.new(
|
||||
'twentyfifteen',
|
||||
target,
|
||||
found_by: 'Css Style (Passive Detection)',
|
||||
found_by: 'Css Style In Homepage (Passive Detection)',
|
||||
confidence: 70,
|
||||
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(
|
||||
'custom',
|
||||
target,
|
||||
found_by: 'Css Style (Passive Detection)',
|
||||
found_by: 'Css Style In Homepage (Passive Detection)',
|
||||
confidence: 70,
|
||||
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(: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(:uniq_links) { false }
|
||||
let(:uniq_codes) { false }
|
||||
@@ -18,6 +19,8 @@ describe WPScan::Finders::MainTheme::UrlsInHomepage do
|
||||
before do
|
||||
stub_request(:get, /.*.css/)
|
||||
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
|
||||
|
||||
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') }
|
||||
|
||||
describe '#passive' do
|
||||
after do
|
||||
stub_request(:get, url).to_return(body: File.read(fixtures.join(@file)))
|
||||
|
||||
expect(finder.passive).to eql @expected
|
||||
before do
|
||||
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)))
|
||||
end
|
||||
|
||||
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
|
||||
@file = 'no_woo_generator.html'
|
||||
@expected = nil
|
||||
expect(finder.passive).to eql nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Woo generator' 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")
|
||||
end
|
||||
|
||||
it 'returns the expected theme' do
|
||||
@file = 'woo_generator.html'
|
||||
@expected = WPScan::Model::Theme.new(
|
||||
'Merchant', target,
|
||||
found_by: 'Woo Framework Meta Generator (Passive Detection)',
|
||||
confidence: 80
|
||||
)
|
||||
context 'from the homepage' do
|
||||
let(:homepage_fixture) { 'woo_generator.html' }
|
||||
let(:error_404_fixture) { 'no_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
|
||||
|
||||
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
|
||||
|
||||
@@ -8,7 +8,7 @@ describe WPScan::Finders::MainTheme::Base do
|
||||
describe '#finders' do
|
||||
it 'contains the expected finders' do
|
||||
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
|
||||
|
||||
49
spec/app/finders/passwords/xml_rpc_spec.rb
Normal file
49
spec/app/finders/passwords/xml_rpc_spec.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe WPScan::Finders::Passwords::XMLRPC do
|
||||
subject(:finder) { described_class.new(target) }
|
||||
let(:target) { WPScan::Model::XMLRPC.new(url) }
|
||||
let(:url) { 'http://ex.lo/xmlrpc.php' }
|
||||
|
||||
RESPONSE_403_BODY = '<?xml version="1.0" encoding="UTF-8"?>
|
||||
<methodResponse>
|
||||
<fault>
|
||||
<value>
|
||||
<struct>
|
||||
<member>
|
||||
<name>faultCode</name>
|
||||
<value><int>403</int></value>
|
||||
</member>
|
||||
<member>
|
||||
<name>faultString</name>
|
||||
<value><string>Incorrect username or password.</string></value>
|
||||
</member>
|
||||
</struct>
|
||||
</value>
|
||||
</fault>
|
||||
</methodResponse>'
|
||||
|
||||
describe '#attack' do
|
||||
context 'when no valid credentials' do
|
||||
before do
|
||||
stub_request(:post, url).to_return(status: status, body: RESPONSE_403_BODY)
|
||||
|
||||
finder.attack(users, %w[pwd])
|
||||
end
|
||||
|
||||
let(:users) { %w[admin].map { |username| WPScan::Model::User.new(username) } }
|
||||
|
||||
context 'when status = 200' do
|
||||
let(:status) { 200 }
|
||||
|
||||
its('progress_bar.log') { should be_empty }
|
||||
end
|
||||
|
||||
context 'when status = 403' do
|
||||
let(:status) { 403 }
|
||||
|
||||
its('progress_bar.log') { should be_empty }
|
||||
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
|
||||
subject(:finder) { described_class.new(target) }
|
||||
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') }
|
||||
|
||||
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(:uniq_links) { 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}" } }
|
||||
end
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ describe WPScan::Finders::Plugins::Base do
|
||||
describe '#finders' do
|
||||
it 'contains the expected finders' do
|
||||
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
|
||||
|
||||
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(: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(:uniq_links) { true }
|
||||
let(:uniq_codes) { true }
|
||||
|
||||
@@ -8,7 +8,7 @@ describe WPScan::Finders::Themes::Base do
|
||||
describe '#finders' do
|
||||
it 'contains the expected finders' do
|
||||
expect(themes.finders.map { |f| f.class.to_s.demodulize })
|
||||
.to eq %w[UrlsInHomepage KnownLocations]
|
||||
.to eq %w[UrlsInHomepage UrlsIn404Page KnownLocations]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,7 +19,7 @@ describe WPScan::Finders::Users::AuthorIdBruteForcing do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#potential_username' do
|
||||
describe '#username_from_response' do
|
||||
[
|
||||
'4.1.1', '4.1.1-permalink',
|
||||
'3.0', '3.0-permalink',
|
||||
@@ -32,6 +32,19 @@ describe WPScan::Finders::Users::AuthorIdBruteForcing do
|
||||
expect(finder.username_from_response(res)).to eql 'admin'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a lot of unrelated links' do
|
||||
it 'should not take a while to process the page' do
|
||||
body = Array.new(300) { |i| "<a href='#{url}#{i}.html'>Some Link</a>" }.join("\n")
|
||||
body << '<a href="https://wp.lab/author/test/">Link</a>'
|
||||
|
||||
time_start = Time.now
|
||||
expect(finder.username_from_response(Typhoeus::Response.new(body: body))).to eql 'test'
|
||||
time_end = Time.now
|
||||
|
||||
expect(time_end - time_start).to be < 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#display_name_from_body' do
|
||||
@@ -50,7 +63,7 @@ describe WPScan::Finders::Users::AuthorIdBruteForcing do
|
||||
end
|
||||
|
||||
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
|
||||
body = File.read(fixtures.join("#{file}-empty.html"))
|
||||
|
||||
|
||||
@@ -16,12 +16,31 @@ describe WPScan::Finders::Users::AuthorPosts do
|
||||
|
||||
results = finder.potential_usernames(res)
|
||||
|
||||
expect(results).to eql([
|
||||
['admin', 'Author Pattern', 100],
|
||||
['admin display_name', 'Display Name', 30],
|
||||
['editor', 'Author Pattern', 100],
|
||||
['editor', 'Display Name', 30]
|
||||
])
|
||||
expect(results).to eql [
|
||||
['admin', 'Author Pattern', 100],
|
||||
['admin display_name', 'Display Name', 30],
|
||||
['editor', 'Author Pattern', 100],
|
||||
['editor', 'Display Name', 30]
|
||||
]
|
||||
end
|
||||
|
||||
context 'when a lot of unrelated uris' do
|
||||
it 'should not take a while to process the page' do
|
||||
body = Array.new(300) { |i| "<a href='#{url}#{i}.html'>Some Link</a>" }.join("\n")
|
||||
body << "<a href='#{url}author/admin/'>Other Link</a>"
|
||||
body << "<a href='#{url}?author=2'>user display name</a>"
|
||||
|
||||
time_start = Time.now
|
||||
results = finder.potential_usernames(Typhoeus::Response.new(body: body))
|
||||
time_end = Time.now
|
||||
|
||||
expect(results).to eql [
|
||||
['admin', 'Author Pattern', 100],
|
||||
['user display name', 'Display Name', 30]
|
||||
]
|
||||
|
||||
expect(time_end - time_start).to be < 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,15 +19,17 @@ describe WPScan::Finders::Users::OembedApi do
|
||||
end
|
||||
|
||||
context 'when a JSON response' do
|
||||
let(:body) { File.read(fixture) }
|
||||
|
||||
context 'when 404' do
|
||||
let(:body) { File.read(fixtures.join('404.json')) }
|
||||
let(:fixture) { fixtures.join('404.json') }
|
||||
|
||||
its(:aggressive) { should eql([]) }
|
||||
end
|
||||
|
||||
context 'when 200' 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
|
||||
users = finder.aggressive
|
||||
@@ -44,7 +46,7 @@ describe WPScan::Finders::Users::OembedApi do
|
||||
end
|
||||
|
||||
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
|
||||
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']
|
||||
end
|
||||
end
|
||||
|
||||
context 'when body is an array' do
|
||||
let(:fixture) { fixtures.join('array.json') }
|
||||
|
||||
its(:aggressive) { should eql([]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -87,6 +87,8 @@ describe WPScan::Finders::Users::WpJsonApi do
|
||||
describe '#api_url' do
|
||||
let(:fixtures) { super().join('api_url') }
|
||||
|
||||
before { allow(target).to receive(:sub_dir).and_return(false) }
|
||||
|
||||
context 'when url in the homepage' do
|
||||
{
|
||||
in_scope: 'https://wp.lab/wp-json/wp/v2/users/',
|
||||
@@ -100,7 +102,7 @@ describe WPScan::Finders::Users::WpJsonApi do
|
||||
end
|
||||
|
||||
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/',
|
||||
|
||||
@@ -10,10 +10,9 @@ describe WPScan::Model::Theme do
|
||||
before { expect(blog).to receive(:content_dir).at_least(1).and_return('wp-content') }
|
||||
|
||||
describe '#new' do
|
||||
before do
|
||||
stub_request(:get, /.*\.css\z/)
|
||||
.to_return(body: File.read(fixtures.join('style.css')))
|
||||
end
|
||||
before { stub_request(:get, /.*\.css\z/).to_return(body: File.read(fixture)) }
|
||||
|
||||
let(:fixture) { fixtures.join('style.css') }
|
||||
|
||||
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' }
|
||||
@@ -34,6 +33,14 @@ describe WPScan::Model::Theme do
|
||||
|
||||
its(:style_url) { should eql opts[:style_url] }
|
||||
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
|
||||
|
||||
describe '#version' do
|
||||
|
||||
@@ -46,7 +46,7 @@ describe WPScan::Model::WpItem do
|
||||
end
|
||||
|
||||
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"
|
||||
end
|
||||
end
|
||||
|
||||
2
spec/cache/.gitignore
vendored
2
spec/cache/.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
!.gitignore
|
||||
|
||||
34767
spec/fixtures/db/dynamic_finders.yml
vendored
34767
spec/fixtures/db/dynamic_finders.yml
vendored
File diff suppressed because it is too large
Load Diff
5079
spec/fixtures/dynamic_finders/expected.yml
vendored
5079
spec/fixtures/dynamic_finders/expected.yml
vendored
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Woo Pelecard v1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-07-07 07:09+0200\n"
|
||||
"PO-Revision-Date: 2014-07-07 07:09+0200\n"
|
||||
"Last-Translator: Tzvi Rabinovitch <tzvi.ra@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: he_IL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Poedit 1.5.4\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
"X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;"
|
||||
"_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2\n"
|
||||
"X-Poedit-Basepath: .\n"
|
||||
"X-Textdomain-Support: yes\n"
|
||||
"X-Poedit-SearchPath-0: ..\n"
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:18
|
||||
msgid "Settings saved."
|
||||
msgstr "שמרתי את ההגדרות."
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:23
|
||||
msgid "10Bit EasyCard PayButtons Settings"
|
||||
msgstr "כפתורי תשלום איזיקארד"
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:37
|
||||
msgid "Client ID"
|
||||
msgstr "מזהה לקוח"
|
||||
|
||||
# @ TenBit_woo_pelecard
|
||||
#: ../10bit-easycard-paybuttons-options.php:45
|
||||
msgid "Password"
|
||||
msgstr "סיסמה"
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:53
|
||||
msgid "Maximum Payments"
|
||||
msgstr "מספר תשלמים מקסימלי"
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:64
|
||||
msgid "Save Changes"
|
||||
msgstr "שמור שינויים"
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:70
|
||||
msgid "Usage :"
|
||||
msgstr "שימוש"
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:72
|
||||
msgid "Add the folloing short code inside a post or a page :"
|
||||
msgstr "הוסיפו את השורטקוד הבא לדף או לעמוד באתר"
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:76
|
||||
msgid "value : The amount to pay"
|
||||
msgstr "value : הסכום לתשלום"
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:78
|
||||
msgid "item_name : the name of the sold item"
|
||||
msgstr "item_name : שם המוצר למכירה"
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:80
|
||||
msgid "button_class : CSS class for styling"
|
||||
msgstr "button_class : קלאס CSS לעיצוב"
|
||||
|
||||
#: ../10bit-easycard-paybuttons-options.php:82
|
||||
msgid "button_text : Text to show on the button"
|
||||
msgstr "button_text : הטקסט שיופיע על הכפתור"
|
||||
|
||||
#: ../10bit-paybuttons-easycard.php:29
|
||||
msgid "EasyCard Pay Buttons"
|
||||
msgstr "כפתורי תשלום איזיקארד"
|
||||
|
||||
# @ TenBit_woo_pelecard
|
||||
#~ msgid "License Key"
|
||||
#~ msgstr "מפתח"
|
||||
|
||||
#~ msgid "Valid"
|
||||
#~ msgstr "תקין"
|
||||
|
||||
#~ msgid "Invalid"
|
||||
#~ msgstr "לא תקין"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "Leave blank for not sending a copy"
|
||||
#~ msgstr "השאר ריק במידה ואינך מעוניים בהעתק"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "10Bit iCount Settings"
|
||||
#~ msgstr "הגדרות icount"
|
||||
|
||||
#~ msgid "Easycard"
|
||||
#~ msgstr "איזיקארד"
|
||||
@@ -0,0 +1,132 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Woo Pelecard v1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-07-09 01:31+0200\n"
|
||||
"PO-Revision-Date: 2014-07-09 01:32+0200\n"
|
||||
"Last-Translator: Tzvi Rabinovitch <tzvi.ra@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: he_IL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Poedit 1.5.4\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
"X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;"
|
||||
"_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2\n"
|
||||
"X-Poedit-Basepath: .\n"
|
||||
"X-Textdomain-Support: yes\n"
|
||||
"X-Poedit-SearchPath-0: ..\n"
|
||||
|
||||
# @ TenBit_woo_pelecard
|
||||
#: ../10bit-paybuttons-pelecard.php:28
|
||||
msgid "pelecard Pay Buttons"
|
||||
msgstr "כפתורי תשלום פלאכארד"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:68
|
||||
msgid "Settings saved."
|
||||
msgstr "שמרתי את ההגדרות."
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:73
|
||||
msgid "10Bit pelecard PayButtons Settings"
|
||||
msgstr "כפתורי תשלום פלאכארד"
|
||||
|
||||
# @ TenBit_woo_pelecard
|
||||
#: ../10bit-pelecard-paybuttons-options.php:87
|
||||
msgid "Terminal Number"
|
||||
msgstr "מספר מסוף סליקה"
|
||||
|
||||
# @ TenBit_woo_pelecard
|
||||
#: ../10bit-pelecard-paybuttons-options.php:95
|
||||
msgid "Username"
|
||||
msgstr "שם משתמש"
|
||||
|
||||
# @ TenBit_woo_pelecard
|
||||
#: ../10bit-pelecard-paybuttons-options.php:103
|
||||
msgid "Password"
|
||||
msgstr "סיסמה"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:111
|
||||
msgid "Maximum Payments"
|
||||
msgstr "מספר תשלמים מקסימלי"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:119
|
||||
msgid "Currency"
|
||||
msgstr "מטבע"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:123
|
||||
msgid "ILS"
|
||||
msgstr "ש\"ח"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:124
|
||||
msgid "USD"
|
||||
msgstr "דולר"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:130
|
||||
msgid "Success ( thank you ) page URL"
|
||||
msgstr "כתובת הצלחה ( דף תודה )"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:138
|
||||
msgid "Error page URL"
|
||||
msgstr "כתובת כשלון"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:146
|
||||
msgid "Path to custom logo"
|
||||
msgstr "קישור ללוגו "
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:154
|
||||
msgid "Path to small custom logo"
|
||||
msgstr "קישור ללוגו קטן "
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:162
|
||||
msgid "Hide PCI DSS logo"
|
||||
msgstr "הסתר את הלוגו של PCI DSS"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:170
|
||||
msgid "Hide Pelecard logo"
|
||||
msgstr "הסתר את לוגו פלאכארד"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:178
|
||||
msgid "Path To custom Style sheet(CSS)"
|
||||
msgstr "כתובת כשלון"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:183
|
||||
msgid "must be a secured URL ( HTTPS )"
|
||||
msgstr "חייב להיות מכתובת מאובטחת (HTTPS )"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:189
|
||||
msgid "Background color"
|
||||
msgstr "צבע רקע"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:195
|
||||
msgid "Use hexadecimal color without the #"
|
||||
msgstr "ערך הקסדצימלי ללא #"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:217
|
||||
msgid "Save Changes"
|
||||
msgstr "שמור שינויים"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:223
|
||||
msgid "Usage :"
|
||||
msgstr "שימוש"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:225
|
||||
msgid "Add the following short code inside a post or a page :"
|
||||
msgstr "הוסיפו את השורטקוד הבא לדף או לעמוד באתר"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:229
|
||||
msgid "value : The amount to pay"
|
||||
msgstr "value : הסכום לתשלום"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:231
|
||||
msgid "item_name : the name of the sold item"
|
||||
msgstr "item_name : שם המוצר למכירה"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:233
|
||||
msgid "button_class : CSS class for styling"
|
||||
msgstr "button_class : קלאס CSS לעיצוב"
|
||||
|
||||
#: ../10bit-pelecard-paybuttons-options.php:235
|
||||
msgid "button_text : Text to show on the button"
|
||||
msgstr "button_text : הטקסט שיופיע על הכפתור"
|
||||
@@ -0,0 +1,24 @@
|
||||
# Italian translation for ab-human-time
|
||||
# Copyright (C) 2014 AB Human Time
|
||||
# This file is distributed under the same license as the AB Human Time package.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: AB Human Time 0.1\n"
|
||||
"POT-Creation-Date: 2014-02-26 20:13+0100\n"
|
||||
"PO-Revision-Date: 2014-02-26 21:25+0100\n"
|
||||
"Last-Translator: endrix.develop <endrix.develop@gmail.com>\n"
|
||||
"Language-Team: <endrix.develop@gmail.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 1.6.4\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Language: it_IT\n"
|
||||
|
||||
#: ../ab-human-time.php:21
|
||||
msgid "Published "
|
||||
msgstr "Pubblicato "
|
||||
|
||||
#: ../ab-human-time.php:23
|
||||
msgid " ago"
|
||||
msgstr " fa"
|
||||
@@ -0,0 +1,49 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: xq-xe-xt-xy 1.0\n"
|
||||
"POT-Creation-Date: 2018-07-11 09:44+0300\n"
|
||||
"PO-Revision-Date: 2018-07-11 09:44+0300\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Marko Maksym\n"
|
||||
"Language: uk_UA\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-Basepath: ../includes\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"X-Poedit-KeywordsList: __;_e\n"
|
||||
"X-Poedit-SearchPath-0: .\n"
|
||||
|
||||
#: admin/class-admin-main.php:66
|
||||
msgid "Title of the page"
|
||||
msgstr ""
|
||||
|
||||
#: admin/class-admin-main.php:66
|
||||
msgid "Link Name"
|
||||
msgstr ""
|
||||
|
||||
#: admin/class-admin-main.php:69
|
||||
msgid "Submenu title"
|
||||
msgstr ""
|
||||
|
||||
#: admin/class-admin-main.php:69
|
||||
msgid "Submenu item"
|
||||
msgstr ""
|
||||
|
||||
#: admin/templates/index.php:8
|
||||
msgid "Settings Page"
|
||||
msgstr ""
|
||||
|
||||
#: admin/templates/main_module_menu.php:10
|
||||
msgid "Main page"
|
||||
msgstr ""
|
||||
|
||||
#: admin/templates/main_module_menu.php:13 admin/templates/page1.php:8
|
||||
msgid "Page 1"
|
||||
msgstr ""
|
||||
|
||||
#: admin/templates/main_module_menu.php:16 admin/templates/page2.php:8
|
||||
msgid "Page 2"
|
||||
msgstr ""
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user