Compare commits
182 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
025ce37c05 | ||
|
|
d6c2c63679 | ||
|
|
49efbf25ea | ||
|
|
02cdee2776 | ||
|
|
7c9d4d5b05 | ||
|
|
609b7551f8 | ||
|
|
e8f215ae00 | ||
|
|
2e00aea16e | ||
|
|
dd274d77f5 | ||
|
|
58171a7b8c | ||
|
|
8b05179401 | ||
|
|
51d61a7e88 | ||
|
|
d653ce4e0e | ||
|
|
07b3826806 | ||
|
|
1baa3e23b2 | ||
|
|
0aa1f20d47 | ||
|
|
1cf330b389 | ||
|
|
1771c4b346 | ||
|
|
4c053b4873 | ||
|
|
743ba0541b | ||
|
|
cfab2a9cd7 | ||
|
|
32270efd65 | ||
|
|
7ea1acb7c1 | ||
|
|
bf91f60242 | ||
|
|
660885c0b1 | ||
|
|
15fd3b969f | ||
|
|
f1d15ca7f2 | ||
|
|
6f4f4a5924 | ||
|
|
9af0520701 | ||
|
|
2edeab558e | ||
|
|
87bf59f50b | ||
|
|
eeb69e63f7 | ||
|
|
f9435906e7 | ||
|
|
6c8adbe50e | ||
|
|
23bdb6c579 | ||
|
|
264411bfb9 | ||
|
|
2104237584 | ||
|
|
0ae2525737 | ||
|
|
b12973a837 | ||
|
|
fa0582ce0b | ||
|
|
231f5157bf | ||
|
|
8b18204a69 | ||
|
|
95eb6a732c | ||
|
|
047a188b34 | ||
|
|
d407815c30 | ||
|
|
1f0f87633b | ||
|
|
c15ff4e32e | ||
|
|
72bddca314 | ||
|
|
496fc4ebee | ||
|
|
f414e6eeb7 | ||
|
|
f09606cfa3 | ||
|
|
6304fe4c19 | ||
|
|
5f2b8f8a2e | ||
|
|
898e8d4546 | ||
|
|
f1657164d5 | ||
|
|
357e13be2b | ||
|
|
9685568c75 | ||
|
|
b316940790 | ||
|
|
2ced489e1e | ||
|
|
5969fe08d8 | ||
|
|
4a427f1ff6 | ||
|
|
9a3db275f3 | ||
|
|
475dd4d1ff | ||
|
|
57c99c4a34 | ||
|
|
966f5691a2 | ||
|
|
5088ece8a1 | ||
|
|
943d87fe17 | ||
|
|
b5363b2689 | ||
|
|
c15cb16ca8 | ||
|
|
18b7f088fc | ||
|
|
4f9822743c | ||
|
|
e7925de5bc | ||
|
|
27fc6a7279 | ||
|
|
ab5f46e955 | ||
|
|
d30d212cc5 | ||
|
|
adff971d62 | ||
|
|
23b22f71b8 | ||
|
|
fee3671e32 | ||
|
|
26c6be7268 | ||
|
|
01c5bcf2be | ||
|
|
1ab8a5ab98 | ||
|
|
b54aaca28a | ||
|
|
86a29ae000 | ||
|
|
a5dbee93ff | ||
|
|
e0465e6e10 | ||
|
|
7da48b9dd1 | ||
|
|
a64895c3a6 | ||
|
|
21f1a5d4c4 | ||
|
|
d60f79ca33 | ||
|
|
2d5cea5033 | ||
|
|
b0615215fe | ||
|
|
7a0f98b2cb | ||
|
|
cdc1dab4a6 | ||
|
|
431739ab19 | ||
|
|
1780399050 | ||
|
|
eb75d38716 | ||
|
|
06f82d78f4 | ||
|
|
dee4da1c0e | ||
|
|
e341ec7c60 | ||
|
|
9146609e4a | ||
|
|
f90615ca41 | ||
|
|
8a2a6a05ff | ||
|
|
5a787f8ed5 | ||
|
|
a904053002 | ||
|
|
70ecd30dcc | ||
|
|
b0976d7e47 | ||
|
|
bb5e55016c | ||
|
|
abdf285c69 | ||
|
|
fd4da23d4f | ||
|
|
bb8f58c83b | ||
|
|
077da6ae86 | ||
|
|
d5222d7e9a | ||
|
|
01702c127b | ||
|
|
87902cbfb4 | ||
|
|
fcaa393ffe | ||
|
|
18bac6e792 | ||
|
|
9a21efebe3 | ||
|
|
357182ef17 | ||
|
|
5fad540a4c | ||
|
|
c1fc153420 | ||
|
|
73a1974f85 | ||
|
|
dec73c21b6 | ||
|
|
46a00cc864 | ||
|
|
62455be165 | ||
|
|
17ef5ef918 | ||
|
|
922b6fffd0 | ||
|
|
b47bf006d0 | ||
|
|
d60269f4bc | ||
|
|
1ce057a78e | ||
|
|
a0fe04b990 | ||
|
|
31c9172e19 | ||
|
|
7f23cbef71 | ||
|
|
4884defaed | ||
|
|
3039218c40 | ||
|
|
8bbc2f32ae | ||
|
|
4ca46ab3ba | ||
|
|
7442c72d01 | ||
|
|
01cd8350bc | ||
|
|
8b5ea589db | ||
|
|
3555ca1d1e | ||
|
|
ae034a47ed | ||
|
|
ec3862c930 | ||
|
|
c63804d1c5 | ||
|
|
c5e6752f75 | ||
|
|
e4f3e9d11c | ||
|
|
f3713536b9 | ||
|
|
fb751c0a51 | ||
|
|
9d3464055a | ||
|
|
0fea814f5d | ||
|
|
ae70a6df9d | ||
|
|
4afc756ccd | ||
|
|
adc5841261 | ||
|
|
41cca5fb8a | ||
|
|
498da1a06b | ||
|
|
48dab90313 | ||
|
|
d1ff642957 | ||
|
|
2b5613d84a | ||
|
|
09d28fae26 | ||
|
|
7517e247d9 | ||
|
|
998951e629 | ||
|
|
d89fcbb68a | ||
|
|
d3e0ff1e66 | ||
|
|
804a8c34c6 | ||
|
|
57942e1826 | ||
|
|
5657735b55 | ||
|
|
791fce2424 | ||
|
|
c34fa45875 | ||
|
|
e0fd79f800 | ||
|
|
f9d9cda4a4 | ||
|
|
d6f44b2f42 | ||
|
|
bd90da7ed2 | ||
|
|
3a1a976e35 | ||
|
|
db1309af83 | ||
|
|
0e47441a36 | ||
|
|
375bea9a8b | ||
|
|
3a42772879 | ||
|
|
e9956593dc | ||
|
|
fda6000c4c | ||
|
|
99b4eb969d | ||
|
|
dadd55ba32 | ||
|
|
b40e06b2ea | ||
|
|
3f20edc41f |
@@ -8,8 +8,9 @@ spec/
|
|||||||
.*
|
.*
|
||||||
**/*.md
|
**/*.md
|
||||||
*.md
|
*.md
|
||||||
|
!README.md
|
||||||
Dockerfile
|
Dockerfile
|
||||||
**/*.orig
|
**/*.orig
|
||||||
*.orig
|
*.orig
|
||||||
bin/wpscan-docker*
|
bin/wpscan-*
|
||||||
.wpscan/
|
.wpscan/
|
||||||
|
|||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -12,3 +12,15 @@ Gemfile.lock
|
|||||||
_yardoc
|
_yardoc
|
||||||
doc/
|
doc/
|
||||||
.wpscan/
|
.wpscan/
|
||||||
|
|
||||||
|
.ash_history
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Old files from v2
|
||||||
|
cache/
|
||||||
|
data/
|
||||||
|
|
||||||
|
# Profiling reports
|
||||||
|
bin/memprof*.report
|
||||||
|
|||||||
@@ -22,7 +22,5 @@ Metrics/CyclomaticComplexity:
|
|||||||
Max: 8
|
Max: 8
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Style/FrozenStringLiteralComment:
|
|
||||||
Enabled: false
|
|
||||||
Style/FormatStringToken:
|
Style/FormatStringToken:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.5.0
|
2.6.2
|
||||||
|
|||||||
4
.simplecov
Normal file
4
.simplecov
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
SimpleCov.start do
|
||||||
|
add_filter '/spec/'
|
||||||
|
add_filter 'helper'
|
||||||
|
end
|
||||||
11
.travis.yml
11
.travis.yml
@@ -10,16 +10,25 @@ rvm:
|
|||||||
- 2.3.5
|
- 2.3.5
|
||||||
- 2.3.6
|
- 2.3.6
|
||||||
- 2.3.7
|
- 2.3.7
|
||||||
|
- 2.3.8
|
||||||
- 2.4.1
|
- 2.4.1
|
||||||
- 2.4.2
|
- 2.4.2
|
||||||
- 2.4.3
|
- 2.4.3
|
||||||
- 2.4.4
|
- 2.4.4
|
||||||
|
- 2.4.5
|
||||||
- 2.5.0
|
- 2.5.0
|
||||||
- 2.5.1
|
- 2.5.1
|
||||||
|
- 2.5.2
|
||||||
|
- 2.5.3
|
||||||
|
- 2.5.4
|
||||||
|
- 2.5.5
|
||||||
|
- 2.6.0
|
||||||
|
- 2.6.1
|
||||||
|
- 2.6.2
|
||||||
- ruby-head
|
- ruby-head
|
||||||
before_install:
|
before_install:
|
||||||
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||||
- "gem update --system"
|
- gem update --system
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rvm: ruby-head
|
- rvm: ruby-head
|
||||||
|
|||||||
33
Dockerfile
33
Dockerfile
@@ -1,27 +1,38 @@
|
|||||||
FROM ruby:2.5-alpine
|
FROM ruby:2.6.2-alpine3.9 AS builder
|
||||||
MAINTAINER WPScan Team <team@wpscan.org>
|
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||||
|
|
||||||
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
||||||
|
|
||||||
RUN adduser -h /wpscan -g WPScan -D wpscan
|
|
||||||
RUN echo "gem: --no-ri --no-rdoc" > /etc/gemrc
|
RUN echo "gem: --no-ri --no-rdoc" > /etc/gemrc
|
||||||
|
|
||||||
COPY . /wpscan
|
COPY . /wpscan
|
||||||
RUN chown -R wpscan:wpscan /wpscan
|
|
||||||
|
|
||||||
# runtime dependencies
|
RUN apk add --no-cache git libcurl ruby-dev libffi-dev make gcc musl-dev zlib-dev procps sqlite-dev && \
|
||||||
RUN apk add --no-cache libcurl procps sqlite-libs && \
|
bundle install --system --clean --no-cache --gemfile=/wpscan/Gemfile $BUNDLER_ARGS && \
|
||||||
# build dependencies
|
# temp fix for https://github.com/bundler/bundler/issues/6680
|
||||||
apk add --no-cache --virtual build-deps git libcurl ruby-dev libffi-dev make gcc musl-dev zlib-dev procps sqlite-dev && \
|
rm -rf /usr/local/bundle/cache
|
||||||
bundle install --system --gemfile=/wpscan/Gemfile $BUNDLER_ARGS && \
|
|
||||||
apk del --no-cache build-deps
|
|
||||||
|
|
||||||
WORKDIR /wpscan
|
WORKDIR /wpscan
|
||||||
RUN rake install --trace
|
RUN rake install --trace
|
||||||
|
|
||||||
|
# needed so non superusers can read gems
|
||||||
|
RUN chmod -R a+r /usr/local/bundle
|
||||||
|
|
||||||
|
|
||||||
|
FROM ruby:2.6.2-alpine3.9
|
||||||
|
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||||
|
|
||||||
|
RUN adduser -h /wpscan -g WPScan -D wpscan
|
||||||
|
|
||||||
|
COPY --from=builder /usr/local/bundle /usr/local/bundle
|
||||||
|
RUN chown -R wpscan:wpscan /wpscan
|
||||||
|
|
||||||
|
# runtime dependencies
|
||||||
|
RUN apk add --no-cache libcurl procps sqlite-libs
|
||||||
|
|
||||||
|
|
||||||
USER wpscan
|
USER wpscan
|
||||||
RUN /usr/local/bundle/bin/wpscan --update --verbose
|
RUN /usr/local/bundle/bin/wpscan --update --verbose
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]
|
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]
|
||||||
CMD ["--help"]
|
CMD ["--help"]
|
||||||
|
|
||||||
|
|||||||
2
Gemfile
2
Gemfile
@@ -1,2 +1,4 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
gemspec
|
gemspec
|
||||||
|
|||||||
10
LICENSE
10
LICENSE
@@ -1,14 +1,14 @@
|
|||||||
WPScan Public Source License
|
WPScan Public Source License
|
||||||
|
|
||||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2018 WPScan Team.
|
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2019 WPScan Team.
|
||||||
|
|
||||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
||||||
|
|
||||||
1. Definitions
|
1. Definitions
|
||||||
|
|
||||||
1.1 “License” means this document.
|
1.1 "License" means this document.
|
||||||
1.2 “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
|
1.2 "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
|
||||||
1.3 “WPScan Team” means WPScan’s core developers, an updated list of whom can be found within the CREDITS file.
|
1.3 "WPScan Team" means WPScan’s core developers.
|
||||||
|
|
||||||
2. Commercialization
|
2. Commercialization
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ WPScan is provided under an AS-IS basis and without any support, updates or main
|
|||||||
|
|
||||||
8. Disclaimer of Warranty
|
8. Disclaimer of Warranty
|
||||||
|
|
||||||
WPScan is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the WPScan is free of defects, merchantable, fit for a particular purpose or non-infringing.
|
WPScan is provided under this License on an "as is" basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the WPScan is free of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||||
|
|
||||||
9. Limitation of Liability
|
9. Limitation of Liability
|
||||||
|
|
||||||
|
|||||||
98
README.md
98
README.md
@@ -1,29 +1,49 @@
|
|||||||

|
<p align="center">
|
||||||
|
<a href="https://wpscan.org/">
|
||||||
|
<img src="https://raw.githubusercontent.com/wpscanteam/wpscan/gh-pages/images/wpscan_logo.png" alt="WPScan logo">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
[](https://badge.fury.io/rb/wpscan)
|
<h3 align="center">WPScan</h3>
|
||||||
[](https://travis-ci.org/wpscanteam/wpscan)
|
|
||||||
[](https://codeclimate.com/github/wpscanteam/wpscan)
|
<p align="center">
|
||||||
[](https://www.patreon.com/wpscan)
|
WordPress Vulnerability Scanner
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<a href="https://wpscan.org/" title="homepage" target="_blank">Homepage</a> - <a href="https://wpscan.io/" title="wpscan.io" target="_blank">WPScan.io</a> - <a href="https://wpvulndb.com/" title="vulnerability database" target="_blank">Vulnerability Database</a> - <a href="https://wordpress.org/plugins/wpscan/" title="wordpress plugin" target="_blank">WordPress Plugin</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://badge.fury.io/rb/wpscan" target="_blank"><img src="https://badge.fury.io/rb/wpscan.svg"></a>
|
||||||
|
<a href="https://travis-ci.org/wpscanteam/wpscan" target="_blank"><img src="https://travis-ci.org/wpscanteam/wpscan.svg?branch=master"></a>
|
||||||
|
<a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
|
||||||
|
<a href="https://www.patreon.com/wpscan" target="_blank"><img src="https://img.shields.io/badge/patreon-donate-green.svg"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
# INSTALL
|
# INSTALL
|
||||||
|
|
||||||
## Prerequisites:
|
## Prerequisites
|
||||||
|
|
||||||
- Ruby >= 2.2.2 - Recommended: 2.3.3
|
- (Optional but highly recommended: [RVM](https://rvm.io/rvm/install))
|
||||||
- Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
|
- Ruby >= 2.3 - Recommended: latest
|
||||||
|
- Ruby 2.5.0 to 2.5.3 can cause an 'undefined symbol: rmpd_util_str_to_d' error in some systems, see [#1283](https://github.com/wpscanteam/wpscan/issues/1283)
|
||||||
|
- Curl >= 7.21 - Recommended: latest
|
||||||
|
- The 7.29 has a segfault
|
||||||
- RubyGems - Recommended: latest
|
- RubyGems - Recommended: latest
|
||||||
|
|
||||||
### From RubyGems:
|
### From RubyGems (Recommended)
|
||||||
|
|
||||||
```
|
```shell
|
||||||
gem install wpscan
|
gem install wpscan
|
||||||
```
|
```
|
||||||
|
|
||||||
### From sources:
|
On MacOSX, if a ```Gem::FilePermissionError``` is raised due to the Apple's System Integrity Protection (SIP), either install RVM and install wpscan again, or run ```sudo gem install -n /usr/local/bin wpscan``` (see [#1286](https://github.com/wpscanteam/wpscan/issues/1286))
|
||||||
|
|
||||||
|
### From sources (NOT Recommended)
|
||||||
|
|
||||||
Prerequisites: Git
|
Prerequisites: Git
|
||||||
|
|
||||||
```
|
```shell
|
||||||
git clone https://github.com/wpscanteam/wpscan
|
git clone https://github.com/wpscanteam/wpscan
|
||||||
|
|
||||||
cd wpscan/
|
cd wpscan/
|
||||||
@@ -31,10 +51,30 @@ cd wpscan/
|
|||||||
bundle install && rake install
|
bundle install && rake install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Updating
|
||||||
|
|
||||||
|
You can update the local database by using ```wpscan --update```
|
||||||
|
|
||||||
|
Updating WPScan itself is either done via ```gem update wpscan``` or the packages manager (this is quite important for distributions such as in Kali Linux: ```apt-get update && apt-get upgrade```) depending how WPScan was (pre)installed
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
|
|
||||||
Pull the repo with ```docker pull wpscanteam/wpscan```
|
Pull the repo with ```docker pull wpscanteam/wpscan```
|
||||||
|
|
||||||
|
Enumerating usernames
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u
|
||||||
|
```
|
||||||
|
|
||||||
|
Enumerating a range of usernames
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u1-100
|
||||||
|
```
|
||||||
|
|
||||||
|
** replace u1-100 with a range of your choice.
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings. If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings. If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
||||||
@@ -46,42 +86,50 @@ The DB is located at ~/.wpscan/db
|
|||||||
|
|
||||||
WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
|
WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
|
||||||
|
|
||||||
* ~/.wpscan/cli_options.json
|
- ~/.wpscan/cli_options.json
|
||||||
* ~/.wpscan/cli_options.yml
|
- ~/.wpscan/cli_options.yml
|
||||||
* pwd/.wpscan/cli_options.json
|
- pwd/.wpscan/cli_options.json
|
||||||
* pwd/.wpscan/cli_options.yml
|
- pwd/.wpscan/cli_options.yml
|
||||||
|
|
||||||
If those files exist, options from them will be loaded and overridden if found twice.
|
If those files exist, options from them will be loaded and overridden if found twice.
|
||||||
|
|
||||||
e.g:
|
e.g:
|
||||||
|
|
||||||
~/.wpscan/cli_options.yml:
|
~/.wpscan/cli_options.yml:
|
||||||
```
|
|
||||||
|
```yml
|
||||||
proxy: 'http://127.0.0.1:8080'
|
proxy: 'http://127.0.0.1:8080'
|
||||||
verbose: true
|
verbose: true
|
||||||
```
|
```
|
||||||
|
|
||||||
pwd/.wpscan/cli_options.yml:
|
pwd/.wpscan/cli_options.yml:
|
||||||
```
|
|
||||||
|
```yml
|
||||||
proxy: 'socks5://127.0.0.1:9090'
|
proxy: 'socks5://127.0.0.1:9090'
|
||||||
url: 'http://target.tld'
|
url: 'http://target.tld'
|
||||||
```
|
```
|
||||||
|
|
||||||
Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
|
Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
|
||||||
|
|
||||||
# PROJECT HOME
|
Enumerating usernames
|
||||||
|
|
||||||
[https://wpscan.org](https://wpscan.org)
|
```shell
|
||||||
|
wpscan --url https://target.tld/ --enumerate u
|
||||||
|
```
|
||||||
|
|
||||||
# VULNERABILITY DATABASE
|
Enumerating a range of usernames
|
||||||
|
|
||||||
[https://wpvulndb.com](https://wpvulndb.com)
|
```shell
|
||||||
|
wpscan --url https://target.tld/ --enumerate u1-100
|
||||||
|
```
|
||||||
|
|
||||||
|
** replace u1-100 with a range of your choice.
|
||||||
|
|
||||||
# LICENSE
|
# LICENSE
|
||||||
|
|
||||||
## WPScan Public Source License
|
## WPScan Public Source License
|
||||||
|
|
||||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2018 WPScan Team.
|
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2019 WPScan Team.
|
||||||
|
|
||||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
||||||
|
|
||||||
@@ -91,7 +139,7 @@ Cases that include commercialization of WPScan require a commercial, non-free li
|
|||||||
|
|
||||||
1.2 "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
|
1.2 "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
|
||||||
|
|
||||||
1.3 "WPScan Team" means WPScan’s core developers, an updated list of whom can be found within the CREDITS file.
|
1.3 "WPScan Team" means WPScan’s core developers.
|
||||||
|
|
||||||
### 2. Commercialization
|
### 2. Commercialization
|
||||||
|
|
||||||
@@ -112,8 +160,6 @@ Example cases which do not require a commercial license, and thus fall under the
|
|||||||
|
|
||||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
||||||
|
|
||||||
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
|
||||||
|
|
||||||
Free-use Terms and Conditions;
|
Free-use Terms and Conditions;
|
||||||
|
|
||||||
### 3. Redistribution
|
### 3. Redistribution
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'models'
|
require_relative 'models'
|
||||||
require_relative 'finders'
|
require_relative 'finders'
|
||||||
require_relative 'controllers'
|
require_relative 'controllers'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'controllers/core'
|
require_relative 'controllers/core'
|
||||||
require_relative 'controllers/custom_directories'
|
require_relative 'controllers/custom_directories'
|
||||||
require_relative 'controllers/wp_version'
|
require_relative 'controllers/wp_version'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Controller to add the aliases in the CLI
|
# Controller to add the aliases in the CLI
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Specific Core controller to include WordPress checks
|
# Specific Core controller to include WordPress checks
|
||||||
@@ -5,15 +7,15 @@ module WPScan
|
|||||||
# @return [ Array<OptParseValidator::Opt> ]
|
# @return [ Array<OptParseValidator::Opt> ]
|
||||||
def cli_options
|
def cli_options
|
||||||
[OptURL.new(['--url URL', 'The URL of the blog to scan'],
|
[OptURL.new(['--url URL', 'The URL of the blog to scan'],
|
||||||
required_unless: %i[update help version], default_protocol: 'http')] +
|
required_unless: %i[update help hh version], default_protocol: 'http')] +
|
||||||
super.drop(1) + # delete the --url from CMSScanner
|
super.drop(1) + # delete the --url from CMSScanner
|
||||||
[
|
[
|
||||||
OptChoice.new(['--server SERVER', 'Force the supplied server module to be loaded'],
|
OptChoice.new(['--server SERVER', 'Force the supplied server module to be loaded'],
|
||||||
choices: %w[apache iis nginx],
|
choices: %w[apache iis nginx],
|
||||||
normalize: %i[downcase to_sym]),
|
normalize: %i[downcase to_sym],
|
||||||
|
advanced: true),
|
||||||
OptBoolean.new(['--force', 'Do not check if the target is running WordPress']),
|
OptBoolean.new(['--force', 'Do not check if the target is running WordPress']),
|
||||||
OptBoolean.new(['--[no-]update', 'Wether or not to update the Database'],
|
OptBoolean.new(['--[no-]update', 'Whether or not to update the Database'])
|
||||||
required_unless: %i[url help version])
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ module WPScan
|
|||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def update_db_required?
|
def update_db_required?
|
||||||
if local_db.missing_files?
|
if local_db.missing_files?
|
||||||
raise MissingDatabaseFile if parsed_options[:update] == false
|
raise Error::MissingDatabaseFile if parsed_options[:update] == false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -62,7 +64,7 @@ module WPScan
|
|||||||
# Raises errors if the target is hosted on wordpress.com or is not running WordPress
|
# Raises errors if the target is hosted on wordpress.com or is not running WordPress
|
||||||
# Also check if the homepage_url is still the install url
|
# Also check if the homepage_url is still the install url
|
||||||
def check_wordpress_state
|
def check_wordpress_state
|
||||||
raise WordPressHostedError if target.wordpress_hosted?
|
raise Error::WordPressHosted if target.wordpress_hosted?
|
||||||
|
|
||||||
if Addressable::URI.parse(target.homepage_url).path =~ %r{/wp-admin/install.php$}i
|
if Addressable::URI.parse(target.homepage_url).path =~ %r{/wp-admin/install.php$}i
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ module WPScan
|
|||||||
exit(WPScan::ExitCode::VULNERABLE)
|
exit(WPScan::ExitCode::VULNERABLE)
|
||||||
end
|
end
|
||||||
|
|
||||||
raise NotWordPressError unless target.wordpress? || parsed_options[:force]
|
raise Error::NotWordPress unless target.wordpress?(parsed_options[:detection_mode]) || parsed_options[:force]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Loads the related server module in the target
|
# Loads the related server module in the target
|
||||||
@@ -95,7 +97,7 @@ module WPScan
|
|||||||
mod = CMSScanner::Target::Server.const_get(server)
|
mod = CMSScanner::Target::Server.const_get(server)
|
||||||
|
|
||||||
target.extend mod
|
target.extend mod
|
||||||
WPScan::WpItem.include mod
|
Model::WpItem.include mod
|
||||||
|
|
||||||
server
|
server
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Controller to ensure that the wp-content and wp-plugins
|
# Controller to ensure that the wp-content and wp-plugins
|
||||||
@@ -16,7 +18,7 @@ module WPScan
|
|||||||
|
|
||||||
return if target.content_dir
|
return if target.content_dir
|
||||||
|
|
||||||
raise 'Unable to identify the wp-content dir, please supply it with --wp-content-dir'
|
raise Error::WpContentDirNotDetected
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'enumeration/cli_options'
|
require_relative 'enumeration/cli_options'
|
||||||
require_relative 'enumeration/enum_methods'
|
require_relative 'enumeration/enum_methods'
|
||||||
|
|
||||||
@@ -8,6 +10,10 @@ module WPScan
|
|||||||
def before_scan
|
def before_scan
|
||||||
DB::DynamicFinders::Plugin.create_versions_finders
|
DB::DynamicFinders::Plugin.create_versions_finders
|
||||||
DB::DynamicFinders::Theme.create_versions_finders
|
DB::DynamicFinders::Theme.create_versions_finders
|
||||||
|
|
||||||
|
# Force the Garbage Collector to run due to the above method being
|
||||||
|
# quite heavy in objects allocation
|
||||||
|
GC.start
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Enumeration CLI Options
|
# Enumeration CLI Options
|
||||||
@@ -13,7 +15,7 @@ module WPScan
|
|||||||
def cli_enum_choices
|
def cli_enum_choices
|
||||||
[
|
[
|
||||||
OptMultiChoices.new(
|
OptMultiChoices.new(
|
||||||
['--enumerate [OPTS]', '-e', 'Enumeration Process'],
|
['-e', '--enumerate [OPTS]', 'Enumeration Process'],
|
||||||
choices: {
|
choices: {
|
||||||
vp: OptBoolean.new(['--vulnerable-plugins']),
|
vp: OptBoolean.new(['--vulnerable-plugins']),
|
||||||
ap: OptBoolean.new(['--all-plugins']),
|
ap: OptBoolean.new(['--all-plugins']),
|
||||||
@@ -25,7 +27,10 @@ module WPScan
|
|||||||
cb: OptBoolean.new(['--config-backups']),
|
cb: OptBoolean.new(['--config-backups']),
|
||||||
dbe: OptBoolean.new(['--db-exports']),
|
dbe: OptBoolean.new(['--db-exports']),
|
||||||
u: OptIntegerRange.new(['--users', 'User IDs range. e.g: u1-5'], value_if_empty: '1-10'),
|
u: OptIntegerRange.new(['--users', 'User IDs range. e.g: u1-5'], value_if_empty: '1-10'),
|
||||||
m: OptIntegerRange.new(['--medias', 'Media IDs range. e.g m1-15'], value_if_empty: '1-100')
|
m: OptIntegerRange.new(['--medias',
|
||||||
|
'Media IDs range. e.g m1-15',
|
||||||
|
'Note: Permalink setting must be set to "Plain" for those to be detected'],
|
||||||
|
value_if_empty: '1-100')
|
||||||
},
|
},
|
||||||
value_if_empty: 'vp,vt,tt,cb,dbe,u,m',
|
value_if_empty: 'vp,vt,tt,cb,dbe,u,m',
|
||||||
incompatible: [%i[vp ap p], %i[vt at t]],
|
incompatible: [%i[vp ap p], %i[vt at t]],
|
||||||
@@ -45,7 +50,7 @@ module WPScan
|
|||||||
# @return [ Array<OptParseValidator::OptBase> ]
|
# @return [ Array<OptParseValidator::OptBase> ]
|
||||||
def cli_plugins_opts
|
def cli_plugins_opts
|
||||||
[
|
[
|
||||||
OptSmartList.new(['--plugins-list LIST', 'List of plugins to enumerate']),
|
OptSmartList.new(['--plugins-list LIST', 'List of plugins to enumerate'], advanced: true),
|
||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--plugins-detection MODE',
|
['--plugins-detection MODE',
|
||||||
'Use the supplied mode to enumerate Plugins, instead of the global (--detection-mode) mode.'],
|
'Use the supplied mode to enumerate Plugins, instead of the global (--detection-mode) mode.'],
|
||||||
@@ -54,7 +59,8 @@ module WPScan
|
|||||||
OptBoolean.new(
|
OptBoolean.new(
|
||||||
['--plugins-version-all',
|
['--plugins-version-all',
|
||||||
'Check all the plugins version locations according to the choosen mode (--detection-mode, ' \
|
'Check all the plugins version locations according to the choosen mode (--detection-mode, ' \
|
||||||
'--plugins-detection and --plugins-version-detection)']
|
'--plugins-detection and --plugins-version-detection)'],
|
||||||
|
advanced: true
|
||||||
),
|
),
|
||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--plugins-version-detection MODE',
|
['--plugins-version-detection MODE',
|
||||||
@@ -68,22 +74,23 @@ module WPScan
|
|||||||
# @return [ Array<OptParseValidator::OptBase> ]
|
# @return [ Array<OptParseValidator::OptBase> ]
|
||||||
def cli_themes_opts
|
def cli_themes_opts
|
||||||
[
|
[
|
||||||
OptSmartList.new(['--themes-list LIST', 'List of themes to enumerate']),
|
OptSmartList.new(['--themes-list LIST', 'List of themes to enumerate'], advanced: true),
|
||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--themes-detection MODE',
|
['--themes-detection MODE',
|
||||||
'Use the supplied mode to enumerate Themes, instead of the global (--detection-mode) mode.'],
|
'Use the supplied mode to enumerate Themes, instead of the global (--detection-mode) mode.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
),
|
),
|
||||||
OptBoolean.new(
|
OptBoolean.new(
|
||||||
['--themes-version-all',
|
['--themes-version-all',
|
||||||
'Check all the themes version locations according to the choosen mode (--detection-mode, ' \
|
'Check all the themes version locations according to the choosen mode (--detection-mode, ' \
|
||||||
'--themes-detection and --themes-version-detection)']
|
'--themes-detection and --themes-version-detection)'],
|
||||||
|
advanced: true
|
||||||
),
|
),
|
||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--themes-version-detection MODE',
|
['--themes-version-detection MODE',
|
||||||
'Use the supplied mode to check themes versions instead of the --detection-mode ' \
|
'Use the supplied mode to check themes versions instead of the --detection-mode ' \
|
||||||
'or --themes-detection modes.'],
|
'or --themes-detection modes.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@@ -93,12 +100,12 @@ module WPScan
|
|||||||
[
|
[
|
||||||
OptFilePath.new(
|
OptFilePath.new(
|
||||||
['--timthumbs-list FILE-PATH', 'List of timthumbs\' location to use'],
|
['--timthumbs-list FILE-PATH', 'List of timthumbs\' location to use'],
|
||||||
exists: true, default: File.join(DB_DIR, 'timthumbs-v3.txt')
|
exists: true, default: DB_DIR.join('timthumbs-v3.txt').to_s, advanced: true
|
||||||
),
|
),
|
||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--timthumbs-detection MODE',
|
['--timthumbs-detection MODE',
|
||||||
'Use the supplied mode to enumerate Timthumbs, instead of the global (--detection-mode) mode.'],
|
'Use the supplied mode to enumerate Timthumbs, instead of the global (--detection-mode) mode.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@@ -108,12 +115,12 @@ module WPScan
|
|||||||
[
|
[
|
||||||
OptFilePath.new(
|
OptFilePath.new(
|
||||||
['--config-backups-list FILE-PATH', 'List of config backups\' filenames to use'],
|
['--config-backups-list FILE-PATH', 'List of config backups\' filenames to use'],
|
||||||
exists: true, default: File.join(DB_DIR, 'config_backups.txt')
|
exists: true, default: DB_DIR.join('config_backups.txt').to_s, advanced: true
|
||||||
),
|
),
|
||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--config-backups-detection MODE',
|
['--config-backups-detection MODE',
|
||||||
'Use the supplied mode to enumerate Config Backups, instead of the global (--detection-mode) mode.'],
|
'Use the supplied mode to enumerate Config Backups, instead of the global (--detection-mode) mode.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@@ -123,12 +130,12 @@ module WPScan
|
|||||||
[
|
[
|
||||||
OptFilePath.new(
|
OptFilePath.new(
|
||||||
['--db-exports-list FILE-PATH', 'List of DB exports\' paths to use'],
|
['--db-exports-list FILE-PATH', 'List of DB exports\' paths to use'],
|
||||||
exists: true, default: File.join(DB_DIR, 'db_exports.txt')
|
exists: true, default: DB_DIR.join('db_exports.txt').to_s, advanced: true
|
||||||
),
|
),
|
||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--db-exports-detection MODE',
|
['--db-exports-detection MODE',
|
||||||
'Use the supplied mode to enumerate DB Exports, instead of the global (--detection-mode) mode.'],
|
'Use the supplied mode to enumerate DB Exports, instead of the global (--detection-mode) mode.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@@ -139,7 +146,7 @@ module WPScan
|
|||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--medias-detection MODE',
|
['--medias-detection MODE',
|
||||||
'Use the supplied mode to enumerate Medias, instead of the global (--detection-mode) mode.'],
|
'Use the supplied mode to enumerate Medias, instead of the global (--detection-mode) mode.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@@ -149,12 +156,13 @@ module WPScan
|
|||||||
[
|
[
|
||||||
OptSmartList.new(
|
OptSmartList.new(
|
||||||
['--users-list LIST',
|
['--users-list LIST',
|
||||||
'List of users to check during the users enumeration from the Login Error Messages']
|
'List of users to check during the users enumeration from the Login Error Messages'],
|
||||||
|
advanced: true
|
||||||
),
|
),
|
||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--users-detection MODE',
|
['--users-detection MODE',
|
||||||
'Use the supplied mode to enumerate Users, instead of the global (--detection-mode) mode.'],
|
'Use the supplied mode to enumerate Users, instead of the global (--detection-mode) mode.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Enumeration Methods
|
# Enumeration Methods
|
||||||
class Enumeration < CMSScanner::Controller::Base
|
class Enumeration < CMSScanner::Controller::Base
|
||||||
# @param [ String ] type (plugins or themes)
|
# @param [ String ] type (plugins or themes)
|
||||||
|
# @param [ Symbol ] detection_mode
|
||||||
#
|
#
|
||||||
# @return [ String ] The related enumration message depending on the parsed_options and type supplied
|
# @return [ String ] The related enumration message depending on the parsed_options and type supplied
|
||||||
def enum_message(type)
|
def enum_message(type, detection_mode)
|
||||||
return unless %w[plugins themes].include?(type)
|
return unless %w[plugins themes].include?(type)
|
||||||
|
|
||||||
details = if parsed_options[:enumerate][:"vulnerable_#{type}"]
|
details = if parsed_options[:enumerate][:"vulnerable_#{type}"]
|
||||||
@@ -16,7 +19,20 @@ module WPScan
|
|||||||
'Most Popular'
|
'Most Popular'
|
||||||
end
|
end
|
||||||
|
|
||||||
"Enumerating #{details} #{type.capitalize}"
|
"Enumerating #{details} #{type.capitalize} #{enum_detection_message(detection_mode)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [ Symbol ] detection_mode
|
||||||
|
#
|
||||||
|
# @return [ String ]
|
||||||
|
def enum_detection_message(detection_mode)
|
||||||
|
detection_method = if detection_mode == :mixed
|
||||||
|
'Passive and Aggressive'
|
||||||
|
else
|
||||||
|
detection_mode.to_s.capitalize
|
||||||
|
end
|
||||||
|
|
||||||
|
"(via #{detection_method} Methods)"
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ String ] type (plugins, themes etc)
|
# @param [ String ] type (plugins, themes etc)
|
||||||
@@ -49,12 +65,15 @@ module WPScan
|
|||||||
sort: true
|
sort: true
|
||||||
)
|
)
|
||||||
|
|
||||||
output('@info', msg: enum_message('plugins')) if user_interaction?
|
output('@info', msg: enum_message('plugins', opts[:mode])) if user_interaction?
|
||||||
# Enumerate the plugins & find their versions to avoid doing that when #version
|
# Enumerate the plugins & find their versions to avoid doing that when #version
|
||||||
# is called in the view
|
# is called in the view
|
||||||
plugins = target.plugins(opts)
|
plugins = target.plugins(opts)
|
||||||
|
|
||||||
output('@info', msg: 'Checking Plugin Versions') if user_interaction? && !plugins.empty?
|
if user_interaction? && !plugins.empty?
|
||||||
|
output('@info',
|
||||||
|
msg: "Checking Plugin Versions #{enum_detection_message(opts[:version_detection][:mode])}")
|
||||||
|
end
|
||||||
|
|
||||||
plugins.each(&:version)
|
plugins.each(&:version)
|
||||||
|
|
||||||
@@ -92,12 +111,15 @@ module WPScan
|
|||||||
sort: true
|
sort: true
|
||||||
)
|
)
|
||||||
|
|
||||||
output('@info', msg: enum_message('themes')) if user_interaction?
|
output('@info', msg: enum_message('themes', opts[:mode])) if user_interaction?
|
||||||
# Enumerate the themes & find their versions to avoid doing that when #version
|
# Enumerate the themes & find their versions to avoid doing that when #version
|
||||||
# is called in the view
|
# is called in the view
|
||||||
themes = target.themes(opts)
|
themes = target.themes(opts)
|
||||||
|
|
||||||
output('@info', msg: 'Checking Theme Versions') if user_interaction? && !themes.empty?
|
if user_interaction? && !themes.empty?
|
||||||
|
output('@info',
|
||||||
|
msg: "Checking Theme Versions #{enum_detection_message(opts[:version_detection][:mode])}")
|
||||||
|
end
|
||||||
|
|
||||||
themes.each(&:version)
|
themes.each(&:version)
|
||||||
|
|
||||||
@@ -125,28 +147,33 @@ module WPScan
|
|||||||
def enum_timthumbs
|
def enum_timthumbs
|
||||||
opts = default_opts('timthumbs').merge(list: parsed_options[:timthumbs_list])
|
opts = default_opts('timthumbs').merge(list: parsed_options[:timthumbs_list])
|
||||||
|
|
||||||
output('@info', msg: 'Enumerating Timthumbs') if user_interaction?
|
output('@info', msg: "Enumerating Timthumbs #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||||
output('timthumbs', timthumbs: target.timthumbs(opts))
|
output('timthumbs', timthumbs: target.timthumbs(opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_config_backups
|
def enum_config_backups
|
||||||
opts = default_opts('config_backups').merge(list: parsed_options[:config_backups_list])
|
opts = default_opts('config_backups').merge(list: parsed_options[:config_backups_list])
|
||||||
|
|
||||||
output('@info', msg: 'Enumerating Config Backups') if user_interaction?
|
output('@info', msg: "Enumerating Config Backups #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||||
output('config_backups', config_backups: target.config_backups(opts))
|
output('config_backups', config_backups: target.config_backups(opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_db_exports
|
def enum_db_exports
|
||||||
opts = default_opts('db_exports').merge(list: parsed_options[:db_exports_list])
|
opts = default_opts('db_exports').merge(list: parsed_options[:db_exports_list])
|
||||||
|
|
||||||
output('@info', msg: 'Enumerating DB Exports') if user_interaction?
|
output('@info', msg: "Enumerating DB Exports #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||||
output('db_exports', db_exports: target.db_exports(opts))
|
output('db_exports', db_exports: target.db_exports(opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_medias
|
def enum_medias
|
||||||
opts = default_opts('medias').merge(range: parsed_options[:enumerate][:medias])
|
opts = default_opts('medias').merge(range: parsed_options[:enumerate][:medias])
|
||||||
|
|
||||||
output('@info', msg: 'Enumerating Medias') if user_interaction?
|
if user_interaction?
|
||||||
|
output('@info',
|
||||||
|
msg: "Enumerating Medias #{enum_detection_message(opts[:mode])} "\
|
||||||
|
'(Permalink setting must be set to "Plain" for those to be detected)')
|
||||||
|
end
|
||||||
|
|
||||||
output('medias', medias: target.medias(opts))
|
output('medias', medias: target.medias(opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -163,7 +190,7 @@ module WPScan
|
|||||||
list: parsed_options[:users_list]
|
list: parsed_options[:users_list]
|
||||||
)
|
)
|
||||||
|
|
||||||
output('@info', msg: 'Enumerating Users') if user_interaction?
|
output('@info', msg: "Enumerating Users #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||||
output('users', users: target.users(opts))
|
output('users', users: target.users(opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Main Theme Controller
|
# Main Theme Controller
|
||||||
@@ -7,8 +9,7 @@ module WPScan
|
|||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--main-theme-detection MODE',
|
['--main-theme-detection MODE',
|
||||||
'Use the supplied mode for the Main theme detection, instead of the global (--detection-mode) mode.'],
|
'Use the supplied mode for the Main theme detection, instead of the global (--detection-mode) mode.'],
|
||||||
choices: %w[mixed passive aggressive],
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
normalize: :to_sym
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Password Attack Controller
|
# Password Attack Controller
|
||||||
@@ -52,7 +54,7 @@ module WPScan
|
|||||||
@attacker ||= attacker_from_cli_options || attacker_from_automatic_detection
|
@attacker ||= attacker_from_cli_options || attacker_from_automatic_detection
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ WPScan::XMLRPC ]
|
# @return [ Model::XMLRPC ]
|
||||||
def xmlrpc
|
def xmlrpc
|
||||||
@xmlrpc ||= target.xmlrpc
|
@xmlrpc ||= target.xmlrpc
|
||||||
end
|
end
|
||||||
@@ -65,8 +67,12 @@ module WPScan
|
|||||||
when :wp_login
|
when :wp_login
|
||||||
WPScan::Finders::Passwords::WpLogin.new(target)
|
WPScan::Finders::Passwords::WpLogin.new(target)
|
||||||
when :xmlrpc
|
when :xmlrpc
|
||||||
|
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||||
|
|
||||||
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||||
when :xmlrpc_multicall
|
when :xmlrpc_multicall
|
||||||
|
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||||
|
|
||||||
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -91,7 +97,7 @@ module WPScan
|
|||||||
return target.users unless parsed_options[:usernames]
|
return target.users unless parsed_options[:usernames]
|
||||||
|
|
||||||
parsed_options[:usernames].reduce([]) do |acc, elem|
|
parsed_options[:usernames].reduce([]) do |acc, elem|
|
||||||
acc << CMSScanner::User.new(elem.chomp)
|
acc << Model::User.new(elem.chomp)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Controller
|
module Controller
|
||||||
# Wp Version Controller
|
# Wp Version Controller
|
||||||
class WpVersion < CMSScanner::Controller::Base
|
class WpVersion < CMSScanner::Controller::Base
|
||||||
def cli_options
|
def cli_options
|
||||||
[
|
[
|
||||||
OptBoolean.new(['--wp-version-all', 'Check all the version locations']),
|
OptBoolean.new(['--wp-version-all', 'Check all the version locations'], advanced: true),
|
||||||
OptChoice.new(
|
OptChoice.new(
|
||||||
['--wp-version-detection MODE',
|
['--wp-version-detection MODE',
|
||||||
'Use the supplied mode for the WordPress version detection, ' \
|
'Use the supplied mode for the WordPress version detection, ' \
|
||||||
'instead of the global (--detection-mode) mode.'],
|
'instead of the global (--detection-mode) mode.'],
|
||||||
choices: %w[mixed passive aggressive],
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
normalize: :to_sym
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'finders/interesting_findings'
|
require_relative 'finders/interesting_findings'
|
||||||
require_relative 'finders/wp_items'
|
require_relative 'finders/wp_items'
|
||||||
require_relative 'finders/wp_version'
|
require_relative 'finders/wp_version'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'config_backups/known_filenames'
|
require_relative 'config_backups/known_filenames'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module ConfigBackups
|
module ConfigBackups
|
||||||
@@ -13,11 +15,10 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(potential_urls(opts), opts) do |res|
|
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
||||||
# Might need to improve that
|
|
||||||
next unless res.body =~ /define/i && res.body !~ /<\s?html/i
|
next unless res.body =~ /define/i && res.body !~ /<\s?html/i
|
||||||
|
|
||||||
found << WPScan::ConfigBackup.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
found << Model::ConfigBackup.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'db_exports/known_locations'
|
require_relative 'db_exports/known_locations'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module DbExports
|
module DbExports
|
||||||
@@ -6,6 +8,8 @@ module WPScan
|
|||||||
class KnownLocations < CMSScanner::Finders::Finder
|
class KnownLocations < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
|
SQL_PATTERN = /(?:DROP|(?:UN)?LOCK|CREATE) TABLE|INSERT INTO/.freeze
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ String ] :list
|
# @option opts [ String ] :list
|
||||||
# @option opts [ Boolean ] :show_progression
|
# @option opts [ Boolean ] :show_progression
|
||||||
@@ -14,15 +18,23 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(potential_urls(opts), opts) do |res|
|
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
||||||
next unless res.code == 200 && res.body =~ /INSERT INTO/
|
if res.effective_url.end_with?('.zip')
|
||||||
|
next unless res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
||||||
|
else
|
||||||
|
next unless res.body =~ SQL_PATTERN
|
||||||
|
end
|
||||||
|
|
||||||
found << WPScan::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def full_request_params
|
||||||
|
@full_request_params ||= { headers: { 'Range' => 'bytes=0-3000' } }
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ String ] :list Mandatory
|
# @option opts [ String ] :list Mandatory
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'interesting_findings/readme'
|
require_relative 'interesting_findings/readme'
|
||||||
|
require_relative 'interesting_findings/wp_cron'
|
||||||
require_relative 'interesting_findings/multisite'
|
require_relative 'interesting_findings/multisite'
|
||||||
require_relative 'interesting_findings/debug_log'
|
require_relative 'interesting_findings/debug_log'
|
||||||
require_relative 'interesting_findings/backup_db'
|
require_relative 'interesting_findings/backup_db'
|
||||||
@@ -23,7 +26,7 @@ module WPScan
|
|||||||
%w[
|
%w[
|
||||||
Readme DebugLog FullPathDisclosure BackupDB DuplicatorInstallerLog
|
Readme DebugLog FullPathDisclosure BackupDB DuplicatorInstallerLog
|
||||||
Multisite MuPlugins Registration UploadDirectoryListing TmmDbMigrate
|
Multisite MuPlugins Registration UploadDirectoryListing TmmDbMigrate
|
||||||
UploadSQLDump EmergencyPwdResetScript
|
UploadSQLDump EmergencyPwdResetScript WPCron
|
||||||
].each do |f|
|
].each do |f|
|
||||||
finders << InterestingFindings.const_get(f).new(target)
|
finders << InterestingFindings.const_get(f).new(target)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -6,13 +8,12 @@ module WPScan
|
|||||||
# @return [ InterestingFinding ]
|
# @return [ InterestingFinding ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
path = 'wp-content/backup-db/'
|
path = 'wp-content/backup-db/'
|
||||||
url = target.url(path)
|
res = target.head_and_get(path, [200, 403])
|
||||||
res = Browser.get(url)
|
|
||||||
|
|
||||||
return unless [200, 403].include?(res.code) && !target.homepage_or_404?(res)
|
return unless [200, 403].include?(res.code) && !target.homepage_or_404?(res)
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::BackupDB.new(
|
||||||
url,
|
target.url(path),
|
||||||
confidence: 70,
|
confidence: 70,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
interesting_entries: target.directory_listing_entries(path),
|
interesting_entries: target.directory_listing_entries(path),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -9,9 +11,10 @@ module WPScan
|
|||||||
|
|
||||||
return unless target.debug_log?(path)
|
return unless target.debug_log?(path)
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::DebugLog.new(
|
||||||
target.url(path),
|
target.url(path),
|
||||||
confidence: 100, found_by: DIRECT_ACCESS
|
confidence: 100, found_by: DIRECT_ACCESS,
|
||||||
|
references: { url: 'https://codex.wordpress.org/Debugging_in_WordPress' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -5,13 +7,12 @@ module WPScan
|
|||||||
class DuplicatorInstallerLog < CMSScanner::Finders::Finder
|
class DuplicatorInstallerLog < CMSScanner::Finders::Finder
|
||||||
# @return [ InterestingFinding ]
|
# @return [ InterestingFinding ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
url = target.url('installer-log.txt')
|
path = 'installer-log.txt'
|
||||||
res = Browser.get(url)
|
|
||||||
|
|
||||||
return unless res.body =~ /DUPLICATOR INSTALL-LOG/
|
return unless target.head_and_get(path).body =~ /DUPLICATOR INSTALL-LOG/
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::DuplicatorInstallerLog.new(
|
||||||
url,
|
target.url(path),
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
references: { url: 'https://www.exploit-db.com/ghdb/3981/' }
|
references: { url: 'https://www.exploit-db.com/ghdb/3981/' }
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -5,13 +7,13 @@ module WPScan
|
|||||||
class EmergencyPwdResetScript < CMSScanner::Finders::Finder
|
class EmergencyPwdResetScript < CMSScanner::Finders::Finder
|
||||||
# @return [ InterestingFinding ]
|
# @return [ InterestingFinding ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
url = target.url('/emergency.php')
|
path = 'emergency.php'
|
||||||
res = Browser.get(url)
|
res = target.head_and_get(path)
|
||||||
|
|
||||||
return unless res.code == 200 && !target.homepage_or_404?(res)
|
return unless res.code == 200 && !target.homepage_or_404?(res)
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::EmergencyPwdResetScript.new(
|
||||||
url,
|
target.url(path),
|
||||||
confidence: res.body =~ /password/i ? 100 : 40,
|
confidence: res.body =~ /password/i ? 100 : 40,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
references: {
|
references: {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -10,11 +12,12 @@ module WPScan
|
|||||||
|
|
||||||
return if fpd_entries.empty?
|
return if fpd_entries.empty?
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::FullPathDisclosure.new(
|
||||||
target.url(path),
|
target.url(path),
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
interesting_entries: fpd_entries
|
interesting_entries: fpd_entries,
|
||||||
|
references: { url: 'https://www.owasp.org/index.php/Full_Path_Disclosure' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -12,7 +14,7 @@ module WPScan
|
|||||||
|
|
||||||
url = target.url('wp-content/mu-plugins/')
|
url = target.url('wp-content/mu-plugins/')
|
||||||
|
|
||||||
return WPScan::InterestingFinding.new(
|
return Model::MuPlugins.new(
|
||||||
url,
|
url,
|
||||||
confidence: 70,
|
confidence: 70,
|
||||||
found_by: 'URLs In Homepage (Passive Detection)',
|
found_by: 'URLs In Homepage (Passive Detection)',
|
||||||
@@ -35,7 +37,7 @@ module WPScan
|
|||||||
|
|
||||||
target.mu_plugins = true
|
target.mu_plugins = true
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::MuPlugins.new(
|
||||||
url,
|
url,
|
||||||
confidence: 80,
|
confidence: 80,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -15,7 +17,7 @@ module WPScan
|
|||||||
|
|
||||||
target.multisite = true
|
target.multisite = true
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::Multisite.new(
|
||||||
url,
|
url,
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -5,14 +7,14 @@ module WPScan
|
|||||||
class Readme < CMSScanner::Finders::Finder
|
class Readme < CMSScanner::Finders::Finder
|
||||||
# @return [ InterestingFinding ]
|
# @return [ InterestingFinding ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
potential_files.each do |file|
|
potential_files.each do |path|
|
||||||
url = target.url(file)
|
res = target.head_and_get(path)
|
||||||
res = Browser.get(url)
|
|
||||||
|
|
||||||
if res.code == 200 && res.body =~ /wordpress/i
|
next unless res.code == 200 && res.body =~ /wordpress/i
|
||||||
return WPScan::InterestingFinding.new(url, confidence: 100, found_by: DIRECT_ACCESS)
|
|
||||||
end
|
return Model::Readme.new(target.url(path), confidence: 100, found_by: DIRECT_ACCESS)
|
||||||
end
|
end
|
||||||
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -18,7 +20,7 @@ module WPScan
|
|||||||
|
|
||||||
target.registration_enabled = true
|
target.registration_enabled = true
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::Registration.new(
|
||||||
res.effective_url,
|
res.effective_url,
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -7,11 +9,11 @@ module WPScan
|
|||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
path = 'wp-content/uploads/tmm_db_migrate/tmm_db_migrate.zip'
|
path = 'wp-content/uploads/tmm_db_migrate/tmm_db_migrate.zip'
|
||||||
url = target.url(path)
|
url = target.url(path)
|
||||||
res = Browser.get(url)
|
res = browser.forge_request(url, target.head_or_get_request_params).run
|
||||||
|
|
||||||
return unless res.code == 200 && res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
return unless res.code == 200 && res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::TmmDbMigrate.new(
|
||||||
url,
|
url,
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
@@ -11,7 +13,7 @@ module WPScan
|
|||||||
|
|
||||||
url = target.url(path)
|
url = target.url(path)
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::UploadDirectoryListing.new(
|
||||||
url,
|
url,
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS,
|
found_by: DIRECT_ACCESS,
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module InterestingFindings
|
module InterestingFindings
|
||||||
# UploadSQLDump finder
|
# UploadSQLDump finder
|
||||||
class UploadSQLDump < CMSScanner::Finders::Finder
|
class UploadSQLDump < CMSScanner::Finders::Finder
|
||||||
SQL_PATTERN = /(?:(?:(?:DROP|CREATE) TABLE)|INSERT INTO)/
|
SQL_PATTERN = /(?:DROP|CREATE|(?:UN)?LOCK) TABLE|INSERT INTO/.freeze
|
||||||
|
|
||||||
# @return [ InterestingFinding ]
|
# @return [ InterestingFinding ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
url = dump_url
|
path = 'wp-content/uploads/dump.sql'
|
||||||
res = Browser.get(url)
|
res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } })
|
||||||
|
|
||||||
return unless res.code == 200 && res.body =~ SQL_PATTERN
|
return unless res.body =~ SQL_PATTERN
|
||||||
|
|
||||||
WPScan::InterestingFinding.new(
|
Model::UploadSQLDump.new(
|
||||||
url,
|
target.url(path),
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
found_by: DIRECT_ACCESS
|
found_by: DIRECT_ACCESS
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def dump_url
|
|
||||||
target.url('wp-content/uploads/dump.sql')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
33
app/finders/interesting_findings/wp_cron.rb
Normal file
33
app/finders/interesting_findings/wp_cron.rb
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module Finders
|
||||||
|
module InterestingFindings
|
||||||
|
# wp-cron.php finder
|
||||||
|
class WPCron < CMSScanner::Finders::Finder
|
||||||
|
# @return [ InterestingFinding ]
|
||||||
|
def aggressive(_opts = {})
|
||||||
|
res = Browser.get(wp_cron_url)
|
||||||
|
|
||||||
|
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'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def wp_cron_url
|
||||||
|
@wp_cron_url ||= target.url('wp-cron.php')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'main_theme/css_style'
|
require_relative 'main_theme/css_style'
|
||||||
require_relative 'main_theme/woo_framework_meta_generator'
|
require_relative 'main_theme/woo_framework_meta_generator'
|
||||||
require_relative 'main_theme/urls_in_homepage'
|
require_relative 'main_theme/urls_in_homepage'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module MainTheme
|
module MainTheme
|
||||||
@@ -6,7 +8,7 @@ module WPScan
|
|||||||
include Finders::WpItems::URLsInHomepage
|
include Finders::WpItems::URLsInHomepage
|
||||||
|
|
||||||
def create_theme(slug, style_url, opts)
|
def create_theme(slug, style_url, opts)
|
||||||
WPScan::Theme.new(
|
Model::Theme.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by, confidence: 70, style_url: style_url)
|
opts.merge(found_by: found_by, confidence: 70, style_url: style_url)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module MainTheme
|
module MainTheme
|
||||||
@@ -14,7 +16,7 @@ module WPScan
|
|||||||
slugs = items_from_links('themes', false) + items_from_codes('themes', false)
|
slugs = items_from_links('themes', false) + items_from_codes('themes', false)
|
||||||
|
|
||||||
slugs.each_with_object(Hash.new(0)) { |slug, counts| counts[slug] += 1 }.each do |slug, occurences|
|
slugs.each_with_object(Hash.new(0)) { |slug, counts| counts[slug] += 1 }.each do |slug, occurences|
|
||||||
found << WPScan::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 2 * occurences))
|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 2 * occurences))
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module MainTheme
|
module MainTheme
|
||||||
# From the WooFramework meta generators
|
# From the WooFramework meta generators
|
||||||
class WooFrameworkMetaGenerator < CMSScanner::Finders::Finder
|
class WooFrameworkMetaGenerator < CMSScanner::Finders::Finder
|
||||||
THEME_PATTERN = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?"\s+/?>}
|
THEME_PATTERN = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?"\s+/?>}.freeze
|
||||||
FRAMEWORK_PATTERN = %r{<meta name="generator" content="WooFramework\s?([^"]+)?"\s+/?>}
|
FRAMEWORK_PATTERN = %r{<meta name="generator" content="WooFramework\s?([^"]+)?"\s+/?>}.freeze
|
||||||
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i
|
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze
|
||||||
|
|
||||||
def passive(opts = {})
|
def passive(opts = {})
|
||||||
return unless target.homepage_res.body =~ PATTERN
|
return unless target.homepage_res.body =~ PATTERN
|
||||||
|
|
||||||
WPScan::Theme.new(
|
Model::Theme.new(
|
||||||
Regexp.last_match[1],
|
Regexp.last_match[1],
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by, confidence: 80)
|
opts.merge(found_by: found_by, confidence: 80)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'medias/attachment_brute_forcing'
|
require_relative 'medias/attachment_brute_forcing'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Medias
|
module Medias
|
||||||
# Medias Finder
|
# Medias Finder, see https://github.com/wpscanteam/wpscan/issues/172
|
||||||
class AttachmentBruteForcing < CMSScanner::Finders::Finder
|
class AttachmentBruteForcing < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ module WPScan
|
|||||||
enumerate(target_urls(opts), opts) do |res|
|
enumerate(target_urls(opts), opts) do |res|
|
||||||
next unless res.code == 200
|
next unless res.code == 200
|
||||||
|
|
||||||
found << WPScan::Media.new(res.effective_url, opts.merge(found_by: found_by, confidence: 100))
|
found << Model::Media.new(res.effective_url, opts.merge(found_by: found_by, confidence: 100))
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'passwords/wp_login'
|
require_relative 'passwords/wp_login'
|
||||||
require_relative 'passwords/xml_rpc'
|
require_relative 'passwords/xml_rpc'
|
||||||
require_relative 'passwords/xml_rpc_multicall'
|
require_relative 'passwords/xml_rpc_multicall'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Passwords
|
module Passwords
|
||||||
@@ -10,7 +12,8 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
def valid_credentials?(response)
|
def valid_credentials?(response)
|
||||||
response.code == 302
|
response.code == 302 &&
|
||||||
|
response.headers['Set-Cookie']&.any? { |cookie| cookie =~ /wordpress_logged_in_/i }
|
||||||
end
|
end
|
||||||
|
|
||||||
def errored_response?(response)
|
def errored_response?(response)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Passwords
|
module Passwords
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Passwords
|
module Passwords
|
||||||
@@ -20,13 +22,13 @@ module WPScan
|
|||||||
target.multi_call(methods).run
|
target.multi_call(methods).run
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Array<CMSScanner::User> ] users
|
# @param [ Array<Model::User> ] users
|
||||||
# @param [ Array<String> ] passwords
|
# @param [ Array<String> ] passwords
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ Boolean ] :show_progression
|
# @option opts [ Boolean ] :show_progression
|
||||||
# @option opts [ Integer ] :multicall_max_passwords
|
# @option opts [ Integer ] :multicall_max_passwords
|
||||||
#
|
#
|
||||||
# @yield [ CMSScanner::User ] When a valid combination is found
|
# @yield [ Model::User ] When a valid combination is found
|
||||||
#
|
#
|
||||||
# TODO: Make rubocop happy about metrics etc
|
# TODO: Make rubocop happy about metrics etc
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'plugin_version/readme'
|
require_relative 'plugin_version/readme'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
@@ -7,7 +9,7 @@ module WPScan
|
|||||||
class Base
|
class Base
|
||||||
include CMSScanner::Finders::UniqueFinder
|
include CMSScanner::Finders::UniqueFinder
|
||||||
|
|
||||||
# @param [ WPScan::Plugin ] plugin
|
# @param [ Model::Plugin ] plugin
|
||||||
def initialize(plugin)
|
def initialize(plugin)
|
||||||
finders << PluginVersion::Readme.new(plugin)
|
finders << PluginVersion::Readme.new(plugin)
|
||||||
|
|
||||||
@@ -16,7 +18,7 @@ module WPScan
|
|||||||
|
|
||||||
# Load the finders associated with the plugin
|
# Load the finders associated with the plugin
|
||||||
#
|
#
|
||||||
# @param [ WPScan::Plugin ] plugin
|
# @param [ Model::Plugin ] plugin
|
||||||
def load_specific_finders(plugin)
|
def load_specific_finders(plugin)
|
||||||
module_name = plugin.classify
|
module_name = plugin.classify
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module PluginVersion
|
module PluginVersion
|
||||||
@@ -7,21 +9,23 @@ module WPScan
|
|||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
found_by_msg = 'Readme - %s (Aggressive Detection)'
|
found_by_msg = 'Readme - %s (Aggressive Detection)'
|
||||||
|
|
||||||
WPScan::WpItem::READMES.each do |file|
|
# The target(plugin)#readme_url can't be used directly here
|
||||||
url = target.url(file)
|
# as if the --detection-mode is passive, it will always return nil
|
||||||
res = Browser.get(url)
|
Model::WpItem::READMES.each do |file|
|
||||||
|
res = target.head_and_get(file)
|
||||||
|
|
||||||
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
|
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
|
||||||
|
|
||||||
return numbers.reduce([]) do |a, e|
|
return numbers.reduce([]) do |a, e|
|
||||||
a << WPScan::Version.new(
|
a << Model::Version.new(
|
||||||
e[0],
|
e[0],
|
||||||
found_by: format(found_by_msg, e[1]),
|
found_by: format(found_by_msg, e[1]),
|
||||||
confidence: e[2],
|
confidence: e[2],
|
||||||
interesting_entries: [url]
|
interesting_entries: [res.effective_url]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'plugins/urls_in_homepage'
|
require_relative 'plugins/urls_in_homepage'
|
||||||
require_relative 'plugins/known_locations'
|
require_relative 'plugins/known_locations'
|
||||||
# From the DynamicFinders
|
# From the DynamicFinders
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
@@ -15,7 +17,7 @@ module WPScan
|
|||||||
def process_response(opts, response, slug, klass, config)
|
def process_response(opts, response, slug, klass, config)
|
||||||
return unless response.body =~ config['pattern']
|
return unless response.body =~ config['pattern']
|
||||||
|
|
||||||
Plugin.new(
|
Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
@@ -18,7 +20,7 @@ module WPScan
|
|||||||
|
|
||||||
next unless comment =~ config['pattern']
|
next unless comment =~ config['pattern']
|
||||||
|
|
||||||
return Plugin.new(
|
return Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
@@ -19,7 +21,7 @@ module WPScan
|
|||||||
# when checking for plugins
|
# when checking for plugins
|
||||||
#
|
#
|
||||||
|
|
||||||
Plugin.new(
|
Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
@@ -18,7 +20,7 @@ module WPScan
|
|||||||
configs.each do |klass, config|
|
configs.each do |klass, config|
|
||||||
next unless headers[config['header']] && headers[config['header']].to_s =~ config['pattern']
|
next unless headers[config['header']] && headers[config['header']].to_s =~ config['pattern']
|
||||||
|
|
||||||
found << Plugin.new(
|
found << Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
@@ -16,7 +18,7 @@ module WPScan
|
|||||||
response.html.xpath(config['xpath'] || '//script[not(@src)]').each do |node|
|
response.html.xpath(config['xpath'] || '//script[not(@src)]').each do |node|
|
||||||
next if config['pattern'] && !node.text.match(config['pattern'])
|
next if config['pattern'] && !node.text.match(config['pattern'])
|
||||||
|
|
||||||
return Plugin.new(
|
return Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
@@ -5,6 +7,11 @@ module WPScan
|
|||||||
class KnownLocations < CMSScanner::Finders::Finder
|
class KnownLocations < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
|
# @return [ Array<Integer> ]
|
||||||
|
def valid_response_codes
|
||||||
|
@valid_response_codes ||= [200, 401, 403, 301, 500].freeze
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ String ] :list
|
# @option opts [ String ] :list
|
||||||
#
|
#
|
||||||
@@ -12,12 +19,8 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts) do |res, slug|
|
enumerate(target_urls(opts), opts.merge(check_full_response: [200, 401, 403, 500])) do |_res, slug|
|
||||||
# TODO: follow the location (from enumerate()) and remove the 301 here ?
|
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
# As a result, it might remove false positive due to redirection to the homepage
|
|
||||||
next unless [200, 401, 403, 301].include?(res.code)
|
|
||||||
|
|
||||||
found << WPScan::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
@@ -30,10 +33,9 @@ module WPScan
|
|||||||
def target_urls(opts = {})
|
def target_urls(opts = {})
|
||||||
slugs = opts[:list] || DB::Plugins.vulnerable_slugs
|
slugs = opts[:list] || DB::Plugins.vulnerable_slugs
|
||||||
urls = {}
|
urls = {}
|
||||||
plugins_url = target.plugins_url
|
|
||||||
|
|
||||||
slugs.each do |slug|
|
slugs.each do |slug|
|
||||||
urls["#{plugins_url}#{URI.encode(slug)}/"] = slug
|
urls[target.plugin_url(slug)] = slug
|
||||||
end
|
end
|
||||||
|
|
||||||
urls
|
urls
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
@@ -14,7 +16,7 @@ module WPScan
|
|||||||
found = []
|
found = []
|
||||||
|
|
||||||
(items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.each do |slug|
|
(items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.each do |slug|
|
||||||
found << Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
@@ -16,7 +18,7 @@ module WPScan
|
|||||||
response.html.xpath(config['xpath']).each do |node|
|
response.html.xpath(config['xpath']).each do |node|
|
||||||
next if config['pattern'] && !node.text.match(config['pattern'])
|
next if config['pattern'] && !node.text.match(config['pattern'])
|
||||||
|
|
||||||
return Plugin.new(
|
return Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
target,
|
target,
|
||||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'theme_version/style'
|
require_relative 'theme_version/style'
|
||||||
require_relative 'theme_version/woo_framework_meta_generator'
|
require_relative 'theme_version/woo_framework_meta_generator'
|
||||||
|
|
||||||
@@ -8,7 +10,7 @@ module WPScan
|
|||||||
class Base
|
class Base
|
||||||
include CMSScanner::Finders::UniqueFinder
|
include CMSScanner::Finders::UniqueFinder
|
||||||
|
|
||||||
# @param [ WPScan::Theme ] theme
|
# @param [ Model::Theme ] theme
|
||||||
def initialize(theme)
|
def initialize(theme)
|
||||||
finders <<
|
finders <<
|
||||||
ThemeVersion::Style.new(theme) <<
|
ThemeVersion::Style.new(theme) <<
|
||||||
@@ -19,7 +21,7 @@ module WPScan
|
|||||||
|
|
||||||
# Load the finders associated with the theme
|
# Load the finders associated with the theme
|
||||||
#
|
#
|
||||||
# @param [ WPScan::Theme ] theme
|
# @param [ Model::Theme ] theme
|
||||||
def load_specific_finders(theme)
|
def load_specific_finders(theme)
|
||||||
module_name = theme.classify
|
module_name = theme.classify
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module ThemeVersion
|
module ThemeVersion
|
||||||
@@ -30,7 +32,7 @@ module WPScan
|
|||||||
def style_version
|
def style_version
|
||||||
return unless Browser.get(target.style_url).body =~ /Version:[\t ]*(?!trunk)([0-9a-z\.-]+)/i
|
return unless Browser.get(target.style_url).body =~ /Version:[\t ]*(?!trunk)([0-9a-z\.-]+)/i
|
||||||
|
|
||||||
WPScan::Version.new(
|
Model::Version.new(
|
||||||
Regexp.last_match[1],
|
Regexp.last_match[1],
|
||||||
found_by: found_by,
|
found_by: found_by,
|
||||||
confidence: 80,
|
confidence: 80,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module ThemeVersion
|
module ThemeVersion
|
||||||
@@ -11,7 +13,7 @@ module WPScan
|
|||||||
|
|
||||||
return unless Regexp.last_match[1] == target.slug
|
return unless Regexp.last_match[1] == target.slug
|
||||||
|
|
||||||
WPScan::Version.new(Regexp.last_match[2], found_by: found_by, confidence: 80)
|
Model::Version.new(Regexp.last_match[2], found_by: found_by, confidence: 80)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'themes/urls_in_homepage'
|
require_relative 'themes/urls_in_homepage'
|
||||||
require_relative 'themes/known_locations'
|
require_relative 'themes/known_locations'
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Themes
|
module Themes
|
||||||
@@ -5,6 +7,11 @@ module WPScan
|
|||||||
class KnownLocations < CMSScanner::Finders::Finder
|
class KnownLocations < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
|
# @return [ Array<Integer> ]
|
||||||
|
def valid_response_codes
|
||||||
|
@valid_response_codes ||= [200, 401, 403, 301, 500].freeze
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ String ] :list
|
# @option opts [ String ] :list
|
||||||
#
|
#
|
||||||
@@ -12,12 +19,8 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts) do |res, slug|
|
enumerate(target_urls(opts), opts.merge(check_full_response: [200, 401, 403, 500])) do |_res, slug|
|
||||||
# TODO: follow the location (from enumerate()) and remove the 301 here ?
|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
# As a result, it might remove false positive due to redirection to the homepage
|
|
||||||
next unless [200, 401, 403, 301].include?(res.code)
|
|
||||||
|
|
||||||
found << WPScan::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
@@ -30,10 +33,9 @@ module WPScan
|
|||||||
def target_urls(opts = {})
|
def target_urls(opts = {})
|
||||||
slugs = opts[:list] || DB::Themes.vulnerable_slugs
|
slugs = opts[:list] || DB::Themes.vulnerable_slugs
|
||||||
urls = {}
|
urls = {}
|
||||||
themes_url = target.url('wp-content/themes/')
|
|
||||||
|
|
||||||
slugs.each do |slug|
|
slugs.each do |slug|
|
||||||
urls["#{themes_url}#{URI.encode(slug)}/"] = slug
|
urls[target.theme_url(slug)] = slug
|
||||||
end
|
end
|
||||||
|
|
||||||
urls
|
urls
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Themes
|
module Themes
|
||||||
@@ -12,7 +14,7 @@ module WPScan
|
|||||||
found = []
|
found = []
|
||||||
|
|
||||||
(items_from_links('themes') + items_from_codes('themes')).uniq.sort.each do |slug|
|
(items_from_links('themes') + items_from_codes('themes')).uniq.sort.each do |slug|
|
||||||
found << WPScan::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'timthumb_version/bad_request'
|
require_relative 'timthumb_version/bad_request'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
@@ -7,7 +9,7 @@ module WPScan
|
|||||||
class Base
|
class Base
|
||||||
include CMSScanner::Finders::UniqueFinder
|
include CMSScanner::Finders::UniqueFinder
|
||||||
|
|
||||||
# @param [ WPScan::Timthumb ] target
|
# @param [ Model::Timthumb ] target
|
||||||
def initialize(target)
|
def initialize(target)
|
||||||
finders << TimthumbVersion::BadRequest.new(target)
|
finders << TimthumbVersion::BadRequest.new(target)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module TimthumbVersion
|
module TimthumbVersion
|
||||||
@@ -8,7 +10,7 @@ module WPScan
|
|||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
return unless Browser.get(target.url).body =~ /(TimThumb version\s*: ([^<]+))/
|
return unless Browser.get(target.url).body =~ /(TimThumb version\s*: ([^<]+))/
|
||||||
|
|
||||||
WPScan::Version.new(
|
Model::Version.new(
|
||||||
Regexp.last_match[2],
|
Regexp.last_match[2],
|
||||||
found_by: 'Bad Request (Aggressive Detection)',
|
found_by: 'Bad Request (Aggressive Detection)',
|
||||||
confidence: 90,
|
confidence: 90,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'timthumbs/known_locations'
|
require_relative 'timthumbs/known_locations'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Timthumbs
|
module Timthumbs
|
||||||
# Known Locations Timthumbs Finder
|
# Known Locations Timthumbs Finder
|
||||||
|
# Note: A vulnerable version, 2.8.13 can be found here:
|
||||||
|
# https://github.com/GabrielGil/TimThumb/blob/980c3d6a823477761570475e8b83d3e9fcd2d7ae/timthumb.php
|
||||||
class KnownLocations < CMSScanner::Finders::Finder
|
class KnownLocations < CMSScanner::Finders::Finder
|
||||||
include CMSScanner::Finders::Finder::Enumerator
|
include CMSScanner::Finders::Finder::Enumerator
|
||||||
|
|
||||||
|
# @return [ Array<Integer> ]
|
||||||
|
def valid_response_codes
|
||||||
|
@valid_response_codes ||= [400]
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @option opts [ String ] :list Mandatory
|
# @option opts [ String ] :list Mandatory
|
||||||
#
|
#
|
||||||
@@ -12,10 +21,10 @@ module WPScan
|
|||||||
def aggressive(opts = {})
|
def aggressive(opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts) do |res|
|
enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res|
|
||||||
next unless res.code == 400 && res.body =~ /no image specified/i
|
next unless res.body =~ /no image specified/i
|
||||||
|
|
||||||
found << WPScan::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
|
found << Model::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'users/author_posts'
|
require_relative 'users/author_posts'
|
||||||
require_relative 'users/wp_json_api'
|
require_relative 'users/wp_json_api'
|
||||||
require_relative 'users/oembed_api'
|
require_relative 'users/oembed_api'
|
||||||
require_relative 'users/rss_generator'
|
require_relative 'users/rss_generator'
|
||||||
require_relative 'users/author_id_brute_forcing'
|
require_relative 'users/author_id_brute_forcing'
|
||||||
require_relative 'users/login_error_messages'
|
require_relative 'users/login_error_messages'
|
||||||
|
require_relative 'users/yoast_seo_author_sitemap.rb'
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
@@ -19,6 +22,7 @@ module WPScan
|
|||||||
Users::WpJsonApi.new(target) <<
|
Users::WpJsonApi.new(target) <<
|
||||||
Users::OembedApi.new(target) <<
|
Users::OembedApi.new(target) <<
|
||||||
Users::RSSGenerator.new(target) <<
|
Users::RSSGenerator.new(target) <<
|
||||||
|
Users::YoastSeoAuthorSitemap.new(target) <<
|
||||||
Users::AuthorIdBruteForcing.new(target) <<
|
Users::AuthorIdBruteForcing.new(target) <<
|
||||||
Users::LoginErrorMessages.new(target)
|
Users::LoginErrorMessages.new(target)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -18,7 +20,7 @@ module WPScan
|
|||||||
|
|
||||||
next unless username
|
next unless username
|
||||||
|
|
||||||
found << CMSScanner::User.new(
|
found << Model::User.new(
|
||||||
username,
|
username,
|
||||||
id: id,
|
id: id,
|
||||||
found_by: format(found_by_msg, found_by),
|
found_by: format(found_by_msg, found_by),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -10,7 +12,7 @@ module WPScan
|
|||||||
found_by_msg = 'Author Posts - %s (Passive Detection)'
|
found_by_msg = 'Author Posts - %s (Passive Detection)'
|
||||||
|
|
||||||
usernames(opts).reduce([]) do |a, e|
|
usernames(opts).reduce([]) do |a, e|
|
||||||
a << CMSScanner::User.new(
|
a << Model::User.new(
|
||||||
e[0],
|
e[0],
|
||||||
found_by: format(found_by_msg, e[1]),
|
found_by: format(found_by_msg, e[1]),
|
||||||
confidence: e[2]
|
confidence: e[2]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -24,7 +26,7 @@ module WPScan
|
|||||||
|
|
||||||
next unless error =~ /The password you entered for the username|Incorrect Password/i
|
next unless error =~ /The password you entered for the username|Incorrect Password/i
|
||||||
|
|
||||||
found << CMSScanner::User.new(username, found_by: found_by, confidence: 100)
|
found << Model::User.new(username, found_by: found_by, confidence: 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -14,29 +16,35 @@ module WPScan
|
|||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
#
|
#
|
||||||
# TODO: make this code pretty :x
|
|
||||||
#
|
|
||||||
# @return [ Array<User> ]
|
# @return [ Array<User> ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
found = []
|
|
||||||
found_by_msg = 'Oembed API - %s (Aggressive Detection)'
|
|
||||||
|
|
||||||
oembed_data = JSON.parse(Browser.get(api_url).body)
|
oembed_data = JSON.parse(Browser.get(api_url).body)
|
||||||
|
details = user_details_from_oembed_data(oembed_data)
|
||||||
|
|
||||||
|
return [] unless details
|
||||||
|
|
||||||
|
[Model::User.new(details[0],
|
||||||
|
found_by: format(found_by_msg, details[1]),
|
||||||
|
confidence: details[2],
|
||||||
|
interesting_entries: [api_url])]
|
||||||
|
rescue JSON::ParserError
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_details_from_oembed_data(oembed_data)
|
||||||
|
return unless oembed_data
|
||||||
|
|
||||||
if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z}
|
if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z}
|
||||||
details = [Regexp.last_match[1], 'Author URL', 90]
|
details = [Regexp.last_match[1], 'Author URL', 90]
|
||||||
elsif oembed_data['author_name'] && !oembed_data['author_name'].empty?
|
elsif oembed_data['author_name'] && !oembed_data['author_name'].empty?
|
||||||
details = [oembed_data['author_name'].delete(' '), 'Author Name', 70]
|
details = [oembed_data['author_name'], 'Author Name', 70]
|
||||||
end
|
end
|
||||||
|
|
||||||
return unless details
|
details
|
||||||
|
end
|
||||||
|
|
||||||
found << CMSScanner::User.new(details[0],
|
def found_by_msg
|
||||||
found_by: format(found_by_msg, details[1]),
|
'Oembed API - %s (Aggressive Detection)'
|
||||||
confidence: details[2],
|
|
||||||
interesting_entries: [api_url])
|
|
||||||
rescue JSON::ParserError
|
|
||||||
found
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ] The URL of the API listing the Users
|
# @return [ String ] The URL of the API listing the Users
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
@@ -17,20 +19,20 @@ module WPScan
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
res.xml.xpath('//item/dc:creator').each do |node|
|
res.xml.xpath('//item/dc:creator').each do |node|
|
||||||
potential_username = node.text.to_s
|
username = node.text.to_s
|
||||||
|
|
||||||
# Ignoring potential username longer than 60 characters and containing accents
|
# Ignoring potential username longer than 60 characters and containing accents
|
||||||
# as they are considered invalid. See https://github.com/wpscanteam/wpscan/issues/1215
|
# as they are considered invalid. See https://github.com/wpscanteam/wpscan/issues/1215
|
||||||
next if potential_username.length > 60 || potential_username =~ /[^\x00-\x7F]/
|
next if username.strip.empty? || username.length > 60 || username =~ /[^\x00-\x7F]/
|
||||||
|
|
||||||
potential_usernames << potential_username
|
potential_usernames << username
|
||||||
end
|
end
|
||||||
rescue Nokogiri::XML::XPath::SyntaxError
|
rescue Nokogiri::XML::XPath::SyntaxError
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
potential_usernames.uniq.each do |potential_username|
|
potential_usernames.uniq.each do |username|
|
||||||
found << CMSScanner::User.new(potential_username, found_by: found_by, confidence: 50)
|
found << Model::User.new(username, found_by: found_by, confidence: 50)
|
||||||
end
|
end
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,23 +1,34 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module Users
|
module Users
|
||||||
# WP JSON API
|
# WP JSON API
|
||||||
#
|
#
|
||||||
# Since 4.7 - Need more investigation as it seems WP 4.7.1 reduces the exposure, see https://github.com/wpscanteam/wpscan/issues/1038)
|
# Since 4.7 - Need more investigation as it seems WP 4.7.1 reduces the exposure, see https://github.com/wpscanteam/wpscan/issues/1038)
|
||||||
|
# For the pagination, see https://github.com/wpscanteam/wpscan/issues/1285
|
||||||
#
|
#
|
||||||
class WpJsonApi < CMSScanner::Finders::Finder
|
class WpJsonApi < CMSScanner::Finders::Finder
|
||||||
|
MAX_PER_PAGE = 100 # See https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
#
|
#
|
||||||
# @return [ Array<User> ]
|
# @return [ Array<User> ]
|
||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
current_page = 0
|
||||||
|
|
||||||
JSON.parse(Browser.get(api_url).body)&.each do |user|
|
loop do
|
||||||
found << CMSScanner::User.new(user['slug'],
|
current_page += 1
|
||||||
id: user['id'],
|
|
||||||
found_by: found_by,
|
res = Typhoeus.get(api_url, params: { per_page: MAX_PER_PAGE, page: current_page })
|
||||||
confidence: 100,
|
|
||||||
interesting_entries: [api_url])
|
total_pages ||= res.headers['X-WP-TotalPages'].to_i
|
||||||
|
|
||||||
|
users_in_page = users_from_response(res)
|
||||||
|
found += users_in_page
|
||||||
|
|
||||||
|
break if current_page >= total_pages || users_in_page.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
@@ -25,9 +36,34 @@ module WPScan
|
|||||||
found
|
found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param [ Typhoeus::Response ] response
|
||||||
|
#
|
||||||
|
# @return [ Array<User> ] The users from the response
|
||||||
|
def users_from_response(response)
|
||||||
|
found = []
|
||||||
|
|
||||||
|
JSON.parse(response.body)&.each do |user|
|
||||||
|
found << Model::User.new(user['slug'],
|
||||||
|
id: user['id'],
|
||||||
|
found_by: found_by,
|
||||||
|
confidence: 100,
|
||||||
|
interesting_entries: [response.effective_url])
|
||||||
|
end
|
||||||
|
|
||||||
|
found
|
||||||
|
end
|
||||||
|
|
||||||
# @return [ String ] The URL of the API listing the Users
|
# @return [ String ] The URL of the API listing the Users
|
||||||
def api_url
|
def api_url
|
||||||
@api_url ||= target.url('wp-json/wp/v2/users/')
|
return @api_url if @api_url
|
||||||
|
|
||||||
|
target.in_scope_urls(target.homepage_res, "//link[@rel='https://api.w.org/']/@href").each do |url, _tag|
|
||||||
|
uri = Addressable::URI.parse(url.strip)
|
||||||
|
|
||||||
|
return @api_url = uri.join('wp/v2/users/').to_s if uri.path.include?('wp-json')
|
||||||
|
end
|
||||||
|
|
||||||
|
@api_url = target.url('wp-json/wp/v2/users/')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
36
app/finders/users/yoast_seo_author_sitemap.rb
Normal file
36
app/finders/users/yoast_seo_author_sitemap.rb
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module Finders
|
||||||
|
module Users
|
||||||
|
# The YOAST SEO plugin has an author-sitemap.xml which can leak usernames
|
||||||
|
# See https://github.com/wpscanteam/wpscan/issues/1228
|
||||||
|
class YoastSeoAuthorSitemap < CMSScanner::Finders::Finder
|
||||||
|
# @param [ Hash ] opts
|
||||||
|
#
|
||||||
|
# @return [ Array<User> ]
|
||||||
|
def aggressive(_opts = {})
|
||||||
|
found = []
|
||||||
|
|
||||||
|
Browser.get(sitemap_url).html.xpath('//url/loc').each do |user_tag|
|
||||||
|
username = user_tag.text.to_s[%r{/author/([^\/]+)/}, 1]
|
||||||
|
|
||||||
|
next unless username && !username.strip.empty?
|
||||||
|
|
||||||
|
found << Model::User.new(username,
|
||||||
|
found_by: found_by,
|
||||||
|
confidence: 100,
|
||||||
|
interesting_entries: [sitemap_url])
|
||||||
|
end
|
||||||
|
|
||||||
|
found
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ String ] The URL of the author-sitemap
|
||||||
|
def sitemap_url
|
||||||
|
@sitemap_url ||= target.url('author-sitemap.xml')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1 +1,3 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'wp_items/urls_in_homepage'
|
require_relative 'wp_items/urls_in_homepage'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpItems
|
module WpItems
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'wp_version/rss_generator'
|
require_relative 'wp_version/rss_generator'
|
||||||
require_relative 'wp_version/atom_generator'
|
require_relative 'wp_version/atom_generator'
|
||||||
require_relative 'wp_version/rdf_generator'
|
require_relative 'wp_version/rdf_generator'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpVersion
|
module WpVersion
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpVersion
|
module WpVersion
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpVersion
|
module WpVersion
|
||||||
@@ -13,9 +15,9 @@ module WPScan
|
|||||||
|
|
||||||
number = Regexp.last_match(1)
|
number = Regexp.last_match(1)
|
||||||
|
|
||||||
return unless WPScan::WpVersion.valid?(number)
|
return unless Model::WpVersion.valid?(number)
|
||||||
|
|
||||||
WPScan::WpVersion.new(
|
Model::WpVersion.new(
|
||||||
number,
|
number,
|
||||||
found_by: 'Readme (Aggressive Detection)',
|
found_by: 'Readme (Aggressive Detection)',
|
||||||
# Since WP 4.7, the Readme only contains the major version (ie 4.7, 4.8 etc)
|
# Since WP 4.7, the Readme only contains the major version (ie 4.7, 4.8 etc)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpVersion
|
module WpVersion
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
module Finders
|
module Finders
|
||||||
module WpVersion
|
module WpVersion
|
||||||
@@ -11,7 +13,7 @@ module WPScan
|
|||||||
hydra.abort
|
hydra.abort
|
||||||
progress_bar.finish
|
progress_bar.finish
|
||||||
|
|
||||||
return WPScan::WpVersion.new(
|
return Model::WpVersion.new(
|
||||||
version_number,
|
version_number,
|
||||||
found_by: 'Unique Fingerprinting (Aggressive Detection)',
|
found_by: 'Unique Fingerprinting (Aggressive Detection)',
|
||||||
confidence: 100,
|
confidence: 100,
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module Model
|
||||||
|
include CMSScanner::Model
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
require_relative 'models/interesting_finding'
|
require_relative 'models/interesting_finding'
|
||||||
require_relative 'models/wp_version'
|
require_relative 'models/wp_version'
|
||||||
require_relative 'models/xml_rpc'
|
require_relative 'models/xml_rpc'
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
module Model
|
||||||
# Config Backup
|
# Config Backup
|
||||||
class ConfigBackup < InterestingFinding
|
class ConfigBackup < InterestingFinding
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
module Model
|
||||||
# DB Export
|
# DB Export
|
||||||
class DbExport < InterestingFinding
|
class DbExport < InterestingFinding
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -1,6 +1,52 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
module Model
|
||||||
# Custom class to include the WPScan::References module
|
# Custom class to include the WPScan::References module
|
||||||
class InterestingFinding < CMSScanner::InterestingFinding
|
class InterestingFinding < CMSScanner::Model::InterestingFinding
|
||||||
include References
|
include References
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Empty classes for the #type to be correctly displayed (as taken from the self.class from the parent)
|
||||||
|
#
|
||||||
|
class BackupDB < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class DebugLog < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class DuplicatorInstallerLog < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class EmergencyPwdResetScript < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class FullPathDisclosure < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class MuPlugins < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class Multisite < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class Readme < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class Registration < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class TmmDbMigrate < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class UploadDirectoryListing < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class UploadSQLDump < InterestingFinding
|
||||||
|
end
|
||||||
|
|
||||||
|
class WPCron < InterestingFinding
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
module Model
|
||||||
# Media
|
# Media
|
||||||
class Media < InterestingFinding
|
class Media < InterestingFinding
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
module Model
|
||||||
# WordPress Plugin
|
# WordPress Plugin
|
||||||
class Plugin < WpItem
|
class Plugin < WpItem
|
||||||
# See WpItem
|
# See WpItem
|
||||||
def initialize(slug, blog, opts = {})
|
def initialize(slug, blog, opts = {})
|
||||||
super(slug, blog, opts)
|
super(slug, blog, opts)
|
||||||
|
|
||||||
@uri = Addressable::URI.parse(blog.url("wp-content/plugins/#{slug}/"))
|
# To be used by #head_and_get
|
||||||
|
# If custom wp-content, it will be replaced by blog#url
|
||||||
|
@path_from_blog = "wp-content/plugins/#{slug}/"
|
||||||
|
|
||||||
|
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def db_data
|
def db_data
|
||||||
DB::Plugin.db_data(slug)
|
@db_data ||= DB::Plugin.db_data(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
#
|
#
|
||||||
# @return [ WPScan::Version, false ]
|
# @return [ Model::Version, false ]
|
||||||
def version(opts = {})
|
def version(opts = {})
|
||||||
@version = Finders::PluginVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
@version = Finders::PluginVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
||||||
|
|
||||||
@@ -23,3 +30,4 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
module Model
|
||||||
# WordPress Theme
|
# WordPress Theme
|
||||||
class Theme < WpItem
|
class Theme < WpItem
|
||||||
attr_reader :style_url, :style_name, :style_uri, :author, :author_uri, :template, :description,
|
attr_reader :style_url, :style_name, :style_uri, :author, :author_uri, :template, :description,
|
||||||
@@ -8,7 +11,11 @@ module WPScan
|
|||||||
def initialize(slug, blog, opts = {})
|
def initialize(slug, blog, opts = {})
|
||||||
super(slug, blog, opts)
|
super(slug, blog, opts)
|
||||||
|
|
||||||
@uri = Addressable::URI.parse(blog.url("wp-content/themes/#{slug}/"))
|
# To be used by #head_and_get
|
||||||
|
# If custom wp-content, it will be replaced by blog#url
|
||||||
|
@path_from_blog = "wp-content/themes/#{slug}/"
|
||||||
|
|
||||||
|
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
||||||
@style_url = opts[:style_url] || url('style.css')
|
@style_url = opts[:style_url] || url('style.css')
|
||||||
|
|
||||||
parse_style
|
parse_style
|
||||||
@@ -16,12 +23,12 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def db_data
|
def db_data
|
||||||
DB::Theme.db_data(slug)
|
@db_data ||= DB::Theme.db_data(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
#
|
#
|
||||||
# @return [ WPScan::Version, false ]
|
# @return [ Model::Version, false ]
|
||||||
def version(opts = {})
|
def version(opts = {})
|
||||||
@version = Finders::ThemeVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
@version = Finders::ThemeVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
||||||
|
|
||||||
@@ -97,3 +104,4 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
module Model
|
||||||
# Timthumb
|
# Timthumb
|
||||||
class Timthumb < InterestingFinding
|
class Timthumb < InterestingFinding
|
||||||
include Vulnerable
|
include Vulnerable
|
||||||
@@ -16,7 +19,7 @@ module WPScan
|
|||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
#
|
#
|
||||||
# @return [ WPScan::Version, false ]
|
# @return [ Model::Version, false ]
|
||||||
def version(opts = {})
|
def version(opts = {})
|
||||||
@version = Finders::TimthumbVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
@version = Finders::TimthumbVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
|
||||||
|
|
||||||
@@ -69,3 +72,4 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
module Model
|
||||||
# WpItem (superclass of Plugin & Theme)
|
# WpItem (superclass of Plugin & Theme)
|
||||||
class WpItem
|
class WpItem
|
||||||
include Vulnerable
|
include Vulnerable
|
||||||
@@ -7,11 +10,10 @@ module WPScan
|
|||||||
include CMSScanner::Target::Server::Generic
|
include CMSScanner::Target::Server::Generic
|
||||||
|
|
||||||
READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
|
READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
|
||||||
CHANGELOGS = %w[changelog.txt CHANGELOG.md changelog.md].freeze
|
|
||||||
|
|
||||||
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :db_data
|
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
||||||
|
|
||||||
delegate :homepage_res, :xpath_pattern_from_page, :in_scope_urls, to: :blog
|
delegate :homepage_res, :xpath_pattern_from_page, :in_scope_urls, :head_or_get_params, to: :blog
|
||||||
|
|
||||||
# @param [ String ] slug The plugin/theme slug
|
# @param [ String ] slug The plugin/theme slug
|
||||||
# @param [ Target ] blog The targeted blog
|
# @param [ Target ] blog The targeted blog
|
||||||
@@ -57,7 +59,7 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def latest_version
|
def latest_version
|
||||||
@latest_version ||= db_data['latest_version'] ? WPScan::Version.new(db_data['latest_version']) : nil
|
@latest_version ||= db_data['latest_version'] ? Model::Version.new(db_data['latest_version']) : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Not used anywhere ATM
|
# Not used anywhere ATM
|
||||||
@@ -109,30 +111,19 @@ module WPScan
|
|||||||
@classify ||= classify_slug(slug)
|
@classify ||= classify_slug(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ] The readme url if found
|
# @return [ String, False ] The readme url if found, false otherwise
|
||||||
def readme_url
|
def readme_url
|
||||||
return if detection_opts[:mode] == :passive
|
return if detection_opts[:mode] == :passive
|
||||||
|
|
||||||
if @readme_url.nil?
|
return @readme_url unless @readme_url.nil?
|
||||||
|
|
||||||
READMES.each do |path|
|
READMES.each do |path|
|
||||||
return @readme_url = url(path) if Browser.get(url(path)).code == 200
|
t_url = url(path)
|
||||||
end
|
|
||||||
|
return @readme_url = t_url if Browser.forge_request(t_url, blog.head_or_get_params).run.code == 200
|
||||||
end
|
end
|
||||||
|
|
||||||
@readme_url
|
@readme_url = false
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ String, false ] The changelog urr if found
|
|
||||||
def changelog_url
|
|
||||||
return if detection_opts[:mode] == :passive
|
|
||||||
|
|
||||||
if @changelog_url.nil?
|
|
||||||
CHANGELOGS.each do |path|
|
|
||||||
return @changelog_url = url(path) if Browser.get(url(path)).code == 200
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@changelog_url
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ String ] path
|
# @param [ String ] path
|
||||||
@@ -154,5 +145,26 @@ module WPScan
|
|||||||
|
|
||||||
super(path, params)
|
super(path, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# See CMSScanner::Target#head_and_get
|
||||||
|
#
|
||||||
|
# This is used by the error_log? above in the super()
|
||||||
|
# to have the correct path (ie readme.txt checked from the plugin/theme location
|
||||||
|
# and not from the blog root). Could also be used in finders
|
||||||
|
#
|
||||||
|
# @param [ String ] path
|
||||||
|
# @param [ Array<String> ] codes
|
||||||
|
# @param [ Hash ] params The requests params
|
||||||
|
# @option params [ Hash ] :head Request params for the HEAD
|
||||||
|
# @option params [ hash ] :get Request params for the GET
|
||||||
|
#
|
||||||
|
# @return [ Typhoeus::Response ]
|
||||||
|
def head_and_get(path, codes = [200], params = {})
|
||||||
|
final_path = +@path_from_blog
|
||||||
|
final_path << URI.encode(path) unless path.nil?
|
||||||
|
|
||||||
|
blog.head_and_get(final_path, codes, params)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
module Model
|
||||||
# WP Version
|
# WP Version
|
||||||
class WpVersion < CMSScanner::Version
|
class WpVersion < CMSScanner::Model::Version
|
||||||
include Vulnerable
|
include Vulnerable
|
||||||
|
|
||||||
def initialize(number, opts = {})
|
def initialize(number, opts = {})
|
||||||
raise InvalidWordPressVersion unless WpVersion.valid?(number.to_s)
|
raise Error::InvalidWordPressVersion unless WpVersion.valid?(number.to_s)
|
||||||
|
|
||||||
super(number, opts)
|
super(number, opts)
|
||||||
end
|
end
|
||||||
@@ -23,19 +26,18 @@ module WPScan
|
|||||||
@all_numbers = []
|
@all_numbers = []
|
||||||
|
|
||||||
DB::Fingerprints.wp_fingerprints.each_value do |fp|
|
DB::Fingerprints.wp_fingerprints.each_value do |fp|
|
||||||
fp.each_value do |versions|
|
@all_numbers << fp.values
|
||||||
versions.each do |version|
|
|
||||||
@all_numbers << version unless @all_numbers.include?(version)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @all_numbers.flatten.uniq.sort! {} doesn't produce the same result here.
|
||||||
|
@all_numbers.flatten!
|
||||||
|
@all_numbers.uniq!
|
||||||
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def db_data
|
def db_data
|
||||||
DB::Version.db_data(number)
|
@db_data ||= DB::Version.db_data(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<Vulnerability> ]
|
# @return [ Array<Vulnerability> ]
|
||||||
@@ -50,5 +52,16 @@ module WPScan
|
|||||||
|
|
||||||
@vulnerabilities
|
@vulnerabilities
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def release_date
|
||||||
|
@release_date ||= db_data['release_date'] || 'Unknown'
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def status
|
||||||
|
@status ||= db_data['status'] || 'Unknown'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WPScan
|
module WPScan
|
||||||
|
module Model
|
||||||
# Override of the CMSScanner::XMLRPC to include the references
|
# Override of the CMSScanner::XMLRPC to include the references
|
||||||
class XMLRPC < CMSScanner::XMLRPC
|
class XMLRPC < CMSScanner::Model::XMLRPC
|
||||||
include References # To be able to use the :wpvulndb reference if needed
|
include References # To be able to use the :wpvulndb reference if needed
|
||||||
|
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
@@ -17,3 +20,4 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user