Compare commits
356 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a94b8362a | ||
|
|
a25b493064 | ||
|
|
2acf88d83e | ||
|
|
baf3b4bc2b | ||
|
|
750411d9e1 | ||
|
|
aa7b922d30 | ||
|
|
fd660632e0 | ||
|
|
c7df7265ab | ||
|
|
42685a45b3 | ||
|
|
ce5d26a220 | ||
|
|
0e73774bd9 | ||
|
|
85b491472a | ||
|
|
4b382acbad | ||
|
|
12d15bfc7e | ||
|
|
ea1b6b9c17 | ||
|
|
5cb2d16601 | ||
|
|
913717bcf7 | ||
|
|
99fe1855d9 | ||
|
|
e2eb94be22 | ||
|
|
aca1b487ba | ||
|
|
5820c53d0f | ||
|
|
9298758acd | ||
|
|
a981c2b17b | ||
|
|
a783b53107 | ||
|
|
cf2881fda6 | ||
|
|
59368a72bd | ||
|
|
439900a1ea | ||
|
|
44557797b0 | ||
|
|
ba065d5974 | ||
|
|
105e9cbcac | ||
|
|
fe277c1e89 | ||
|
|
b5e3e6280e | ||
|
|
f90a64ce81 | ||
|
|
b9fa1e3587 | ||
|
|
4333ecb989 | ||
|
|
715d3d4ad6 | ||
|
|
38f70a88ae | ||
|
|
4b4b968710 | ||
|
|
3b94fc49a7 | ||
|
|
e41aab3a80 | ||
|
|
9450ba6cc5 | ||
|
|
ae3c164350 | ||
|
|
24e6820a90 | ||
|
|
0e05f77fb7 | ||
|
|
de960ff9db | ||
|
|
1d0128af72 | ||
|
|
285b1a1733 | ||
|
|
ab67816dd9 | ||
|
|
fea6665876 | ||
|
|
6cbc8c9924 | ||
|
|
f542a50213 | ||
|
|
fa430606ce | ||
|
|
05d27c64be | ||
|
|
0cd680bb29 | ||
|
|
ced94a7338 | ||
|
|
b65a4d0a60 | ||
|
|
2b85b44bd1 | ||
|
|
991c87a89e | ||
|
|
37a72f0c72 | ||
|
|
6c0a21c80d | ||
|
|
dc48008d43 | ||
|
|
5720d29492 | ||
|
|
358f3d59d8 | ||
|
|
b6c6a46d25 | ||
|
|
25c393d557 | ||
|
|
435fb34233 | ||
|
|
2c40913a64 | ||
|
|
e437b952da | ||
|
|
282c595b38 | ||
|
|
c2c8d63e75 | ||
|
|
ad21d97d11 | ||
|
|
5c27c78ed0 | ||
|
|
a53e9a5e12 | ||
|
|
c8036692ee | ||
|
|
b9535a3648 | ||
|
|
651c364fa9 | ||
|
|
958410d4c9 | ||
|
|
e9fba126d2 | ||
|
|
95d39cce5a | ||
|
|
32d9afdf9b | ||
|
|
7e9a4168ff | ||
|
|
9d6415a89b | ||
|
|
1499b07176 | ||
|
|
9c7188a312 | ||
|
|
b63e28c150 | ||
|
|
50d48902cf | ||
|
|
aa6899cbc5 | ||
|
|
94e6b2eab6 | ||
|
|
54c0e79c58 | ||
|
|
859d7f1c60 | ||
|
|
166112209e | ||
|
|
952395d0c1 | ||
|
|
c7061f8a51 | ||
|
|
0c71bce221 | ||
|
|
b2b4eebd78 | ||
|
|
5257a8b997 | ||
|
|
9844f9d8ab | ||
|
|
000f275263 | ||
|
|
e5077c490a | ||
|
|
d76968c15f | ||
|
|
289ef5b0dd | ||
|
|
7ec227873c | ||
|
|
1deccfd477 | ||
|
|
286e6bd51a | ||
|
|
8167fa2e17 | ||
|
|
c960df0bb1 | ||
|
|
ebf8d31c6c | ||
|
|
082ae650fc | ||
|
|
2f5599c863 | ||
|
|
a764bdd993 | ||
|
|
ef46d2c956 | ||
|
|
d2c2c1defb | ||
|
|
dede023ec8 | ||
|
|
d8a9b3aa77 | ||
|
|
ad364e6a2e | ||
|
|
523954e507 | ||
|
|
872bbdb8e0 | ||
|
|
3ca8727b64 | ||
|
|
1d3ca87772 | ||
|
|
90c42f42a1 | ||
|
|
641108e7eb | ||
|
|
0e87384b0a | ||
|
|
5175170c4b | ||
|
|
79864cae7b | ||
|
|
ca5f92ca61 | ||
|
|
d29de83c41 | ||
|
|
1f42ce6e2f | ||
|
|
0dc7128582 | ||
|
|
21f4de2ec1 | ||
|
|
d65567fc8f | ||
|
|
20af778fa1 | ||
|
|
5f77832386 | ||
|
|
6ccfe70775 | ||
|
|
6b0f687abb | ||
|
|
67ba526b5b | ||
|
|
e186ec7534 | ||
|
|
23ef1e75b3 | ||
|
|
8170390f92 | ||
|
|
c148295f64 | ||
|
|
37b99f9baa | ||
|
|
8e4643874d | ||
|
|
0522023fd4 | ||
|
|
711ee730a0 | ||
|
|
f3bd995528 | ||
|
|
beec0bd35a | ||
|
|
9d7f35f3b2 | ||
|
|
c7488e28f7 | ||
|
|
9150e0ca52 | ||
|
|
475288deeb | ||
|
|
82335d7399 | ||
|
|
338eacd63b | ||
|
|
0b9b79f55f | ||
|
|
5303b28957 | ||
|
|
11c05a3590 | ||
|
|
862c0a9014 | ||
|
|
487a483aa6 | ||
|
|
030c20a11b | ||
|
|
ec831f7fed | ||
|
|
50fa79b331 | ||
|
|
edab0e812a | ||
|
|
f0126ca860 | ||
|
|
01261d4d29 | ||
|
|
f97d3436a5 | ||
|
|
0bcb8b4b3b | ||
|
|
489545dd75 | ||
|
|
f6c152f58a | ||
|
|
16734418be | ||
|
|
b17ee20f58 | ||
|
|
aaee6f1e6d | ||
|
|
64d8240b8a | ||
|
|
0a6d430c9f | ||
|
|
7bf0314561 | ||
|
|
409897fec4 | ||
|
|
91b0d20665 | ||
|
|
f6644eebf9 | ||
|
|
88bddd4f87 | ||
|
|
c61b023fb7 | ||
|
|
1b5df8751f | ||
|
|
314c98f101 | ||
|
|
8274e2efe9 | ||
|
|
2bff063805 | ||
|
|
53d9956829 | ||
|
|
6e98678c3c | ||
|
|
f0f21f5ac2 | ||
|
|
aa233b1c4d | ||
|
|
93f9123f45 | ||
|
|
5c710d88e4 | ||
|
|
ded70ff743 | ||
|
|
9df7443aa4 | ||
|
|
8362975691 | ||
|
|
49771419ae | ||
|
|
d344f84824 | ||
|
|
89c0b8d4d0 | ||
|
|
3c74ee8d97 | ||
|
|
785c6efa5b | ||
|
|
4e2bf5322e | ||
|
|
54ed148c87 | ||
|
|
b08e298eba | ||
|
|
89e2088357 | ||
|
|
f3cc35bd74 | ||
|
|
a007d283e5 | ||
|
|
70902aa013 | ||
|
|
91151fc53b | ||
|
|
d4ee82dac5 | ||
|
|
88d3c26113 | ||
|
|
054a4ee6aa | ||
|
|
c291022753 | ||
|
|
2fc488b602 | ||
|
|
009ddd690e | ||
|
|
88b5cd8751 | ||
|
|
cfd19d02b1 | ||
|
|
19ce30d862 | ||
|
|
c6df6e0e89 | ||
|
|
e942a5bcf6 | ||
|
|
c0f5163d07 | ||
|
|
f5aa9f117f | ||
|
|
498d93377d | ||
|
|
52242e706b | ||
|
|
22d69a1bf9 | ||
|
|
0b1fa13696 | ||
|
|
19b15b5327 | ||
|
|
e63e96f5ed | ||
|
|
e8ac8f26a7 | ||
|
|
13e4327de4 | ||
|
|
c22a1ed12a | ||
|
|
be5662b5f1 | ||
|
|
6e840ca920 | ||
|
|
8492190f4c | ||
|
|
93ab6ee2a0 | ||
|
|
7075e01886 | ||
|
|
436a83434c | ||
|
|
d270391b56 | ||
|
|
7f2762eb6f | ||
|
|
2cc5bb0311 | ||
|
|
d697127261 | ||
|
|
825523a851 | ||
|
|
0f3f9cac33 | ||
|
|
f9b545b100 | ||
|
|
943bfc39b3 | ||
|
|
b1a8f445c6 | ||
|
|
5435df4345 | ||
|
|
8e9d29e94f | ||
|
|
1afa761f09 | ||
|
|
d626913ce9 | ||
|
|
9c52e4a5ee | ||
|
|
72c2c1992b | ||
|
|
e1b4b5e8e5 | ||
|
|
0243522854 | ||
|
|
5118c68f45 | ||
|
|
442884b5c5 | ||
|
|
f832e27b49 | ||
|
|
6ce29f73c5 | ||
|
|
920338fb62 | ||
|
|
49d0a9e6d9 | ||
|
|
fe401e622b | ||
|
|
6e32cb0db2 | ||
|
|
73171eb39d | ||
|
|
2e05f4171e | ||
|
|
75b8c303e2 | ||
|
|
bd7a493f1c | ||
|
|
9dada7c8f4 | ||
|
|
fe7aede458 | ||
|
|
cdf2b38780 | ||
|
|
a09dbab6a8 | ||
|
|
49a6d275d2 | ||
|
|
8192a4a215 | ||
|
|
1d6593fd4d | ||
|
|
bf99e31e70 | ||
|
|
5386496bdc | ||
|
|
6451510449 | ||
|
|
cd68aa719c | ||
|
|
b328dc4ff9 | ||
|
|
1e1c79aa56 | ||
|
|
08650ce156 | ||
|
|
a1929719f3 | ||
|
|
d34da72cd3 | ||
|
|
816b18b604 | ||
|
|
a78a13bf3f | ||
|
|
33f8aaf1dc | ||
|
|
26ab95d822 | ||
|
|
cea01d8aa0 | ||
|
|
0e61f1e284 | ||
|
|
ddef061b90 | ||
|
|
addeab8947 | ||
|
|
55dc665404 | ||
|
|
8f8538e9e9 | ||
|
|
348ca55bee | ||
|
|
1bb5bc7f33 | ||
|
|
3be5e1fcf5 | ||
|
|
9df8cc9243 | ||
|
|
e28c84aa34 | ||
|
|
7db6b54761 | ||
|
|
e3a06f5694 | ||
|
|
7c5d15e098 | ||
|
|
d683c0f151 | ||
|
|
1e67fa26ff | ||
|
|
0ae6ef59ec | ||
|
|
e27ef40e0f | ||
|
|
380760d028 | ||
|
|
18cfdafc19 | ||
|
|
0934a2e329 | ||
|
|
d1a320324e | ||
|
|
361c96d746 | ||
|
|
e7dbf9278d | ||
|
|
6564fddb27 | ||
|
|
d382874e86 | ||
|
|
91b30bee9f | ||
|
|
7804aad776 | ||
|
|
b7552ac8aa | ||
|
|
a76c94cccf | ||
|
|
c0ae5c7cad | ||
|
|
cc55b39b83 | ||
|
|
d8a6884ab6 | ||
|
|
5ce3581386 | ||
|
|
2208f2a8c0 | ||
|
|
a4a14c7e63 | ||
|
|
aa464b476c | ||
|
|
3c92712a6e | ||
|
|
fd0c47f5d7 | ||
|
|
c03a44d225 | ||
|
|
d31d45ba71 | ||
|
|
db528b27f4 | ||
|
|
e6d29f6f18 | ||
|
|
e4d6b988ef | ||
|
|
ec68291bf0 | ||
|
|
3a6a451db1 | ||
|
|
7ec095d708 | ||
|
|
57f6206aee | ||
|
|
390f10e83f | ||
|
|
8727935cb2 | ||
|
|
d0e868f556 | ||
|
|
01c357e146 | ||
|
|
a0fed4a9d0 | ||
|
|
c4aed0ec89 | ||
|
|
cc737090a2 | ||
|
|
1652c09e95 | ||
|
|
2538b88579 | ||
|
|
8c2eb63840 | ||
|
|
36df5ee6e4 | ||
|
|
9720b4edf1 | ||
|
|
13d35b7607 | ||
|
|
13c2c51cfd | ||
|
|
f43175b0c3 | ||
|
|
1508aba8b2 | ||
|
|
5414ab05e5 | ||
|
|
bd5d2db634 | ||
|
|
3259dd29d8 | ||
|
|
6e56013a95 | ||
|
|
252f762209 | ||
|
|
15c0448cf1 | ||
|
|
4c800bacaa | ||
|
|
86a73229c0 | ||
|
|
cc41b96e88 | ||
|
|
e16c5584d1 | ||
|
|
94bab3f550 | ||
|
|
9d04b23fb2 |
21
.dockerignore
Normal file
21
.dockerignore
Normal file
@@ -0,0 +1,21 @@
|
||||
.*
|
||||
bin/
|
||||
dev/
|
||||
spec/
|
||||
*.md
|
||||
Dockerfile
|
||||
|
||||
## TEMP
|
||||
.idea/
|
||||
.yardoc/
|
||||
bundle/
|
||||
cache/
|
||||
coverage/
|
||||
git/
|
||||
**/*.md
|
||||
**/*.orig
|
||||
*.orig
|
||||
CREDITS
|
||||
data.zip
|
||||
DISCLAIMER.txt
|
||||
example.conf.json
|
||||
32
.gitignore
vendored
32
.gitignore
vendored
@@ -1,15 +1,21 @@
|
||||
cache
|
||||
coverage
|
||||
# WPScan (If not using ~/.wpscan/)
|
||||
cache/
|
||||
data/
|
||||
log.txt
|
||||
output.txt
|
||||
|
||||
# WPScan (Deployment)
|
||||
debug.log
|
||||
rspec_results.html
|
||||
wordlist.txt
|
||||
|
||||
# OS/IDE Rubbish
|
||||
coverage/
|
||||
.yardoc/
|
||||
.idea/
|
||||
*.sublime-*
|
||||
.*.swp
|
||||
.ash_history
|
||||
.bundle
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
*.sublime-*
|
||||
.idea
|
||||
.*.swp
|
||||
Gemfile.lock
|
||||
log.txt
|
||||
.yardoc
|
||||
debug.log
|
||||
wordlist.txt
|
||||
rspec_results.html
|
||||
data/
|
||||
.DS_Store?
|
||||
@@ -1 +1 @@
|
||||
2.2.2
|
||||
2.5.1
|
||||
|
||||
37
.travis.yml
37
.travis.yml
@@ -1,26 +1,35 @@
|
||||
language: ruby
|
||||
sudo: false
|
||||
cache: bundler
|
||||
rvm:
|
||||
- 1.9.2
|
||||
- 1.9.3
|
||||
- 2.0.0
|
||||
- 2.1.0
|
||||
- 2.1.1
|
||||
- 2.1.2
|
||||
- 2.1.3
|
||||
- 2.1.4
|
||||
- 2.1.5
|
||||
- 2.1.9
|
||||
- 2.2.0
|
||||
- 2.2.1
|
||||
- 2.2.2
|
||||
- 2.2.3
|
||||
- 2.2.4
|
||||
- 2.3.0
|
||||
- 2.3.1
|
||||
- 2.3.2
|
||||
- 2.3.3
|
||||
- 2.4.1
|
||||
- 2.4.2
|
||||
- 2.5.0
|
||||
- 2.5.1
|
||||
- ruby-head
|
||||
before_install:
|
||||
- "env"
|
||||
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||
script: bundle exec rspec
|
||||
- "gem install bundler"
|
||||
- "gem regenerate_binstubs"
|
||||
- "bundle --version"
|
||||
before_script:
|
||||
- "unzip -o $TRAVIS_BUILD_DIR/data.zip -d $HOME/.wpscan/"
|
||||
script:
|
||||
- "bundle exec rspec"
|
||||
notifications:
|
||||
email:
|
||||
- wpscanteam@gmail.com
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: 1.9.2
|
||||
- team@wpscan.org
|
||||
# do not build gh-pages branch
|
||||
branches:
|
||||
except:
|
||||
|
||||
136
CHANGELOG.md
136
CHANGELOG.md
@@ -1,6 +1,135 @@
|
||||
# Changelog
|
||||
## Master
|
||||
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.8...master)
|
||||
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.9.4...master)
|
||||
|
||||
## Version 2.9.4
|
||||
Released: 2018-06-15
|
||||
|
||||
* Updated dependencies and required ruby version
|
||||
* Improved CLI output
|
||||
* Only show readme.html output when wp <= 4.8 #1127
|
||||
* Cleanup README.md
|
||||
* Fix bug "undefined method 'identifier' for nil:NilClass" #1149
|
||||
* Since WP 4.7 readme.html only shows major version #1152
|
||||
* Add checks for humans.txt and security.text (Thank you @g0tmi1k!)
|
||||
* Add offline database update support (Thank you @g0tmi1k!)
|
||||
* Check for API access and /wp-json/'s users output (Thank you @g0tmi1k!)
|
||||
* Add RSS author information (Thank you @g0tmi1k!)
|
||||
* Check HTTP status of each value in /robots.txt (Thank you @g0tmi1k!)
|
||||
* Follow any redirections (e.g. http -> https) (Thank you @g0tmi1k!)
|
||||
* Lots of other enhancements by @g0tmi1k & WPScan Team
|
||||
* Database export file enumeration.
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total tracked wordpresses: 319
|
||||
* Total tracked plugins: 74896
|
||||
* Total tracked themes: 16666
|
||||
* Total vulnerable wordpresses: 305
|
||||
* Total vulnerable plugins: 1645
|
||||
* Total vulnerable themes: 286
|
||||
* Total wordpress vulnerabilities: 8327
|
||||
* Total plugin vulnerabilities: 2603
|
||||
* Total theme vulnerabilities: 352
|
||||
|
||||
## Version 2.9.3
|
||||
Released: 2017-07-19
|
||||
|
||||
* Updated dependencies and required ruby version
|
||||
* Made some changes so wpscan works in ruby 2.4
|
||||
* Added a Gemfile.lock to lock all dependencies
|
||||
* You can now pass a wordlist from stdin via "--wordlist -"
|
||||
* Improved version detection regexes
|
||||
* Added an optional paramter to --log to specify a filename
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total tracked wordpresses: 251
|
||||
* Total tracked plugins: 68818
|
||||
* Total tracked themes: 15132
|
||||
* Total vulnerable wordpresses: 243
|
||||
* Total vulnerable plugins: 1527
|
||||
* Total vulnerable themes: 280
|
||||
* Total wordpress vulnerabilities: 5263
|
||||
* Total plugin vulnerabilities: 2406
|
||||
* Total theme vulnerabilities: 349
|
||||
|
||||
## Version 2.9.2
|
||||
Released: 2016-11-15
|
||||
|
||||
* Fixed error when detecting plugins with UTF-8 characters
|
||||
* Use all possible finders to verify a detected version
|
||||
* Fix error when detecting a WordPress version not in our database
|
||||
* Added some additional clarification on error messages
|
||||
* Upgrade terminal-table gem
|
||||
* Add --cache-dir option
|
||||
* Add --disable-tls-checks options
|
||||
* Improve/add additional plugin passive detections
|
||||
* Remove scripts when calculating page hashes
|
||||
* Many other small bug fixes.
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total tracked wordpresses: 194
|
||||
* Total tracked plugins: 63703
|
||||
* Total tracked themes: 13835
|
||||
* Total vulnerable wordpresses: 177
|
||||
* Total vulnerable plugins: 1382
|
||||
* Total vulnerable themes: 379
|
||||
* Total wordpress vulnerabilities: 2617
|
||||
* Total plugin vulnerabilities: 2190
|
||||
* Total theme vulnerabilities: 452
|
||||
|
||||
## Version 2.9.1
|
||||
Released: 2016-05-06
|
||||
|
||||
* Update to Ruby 2.3.1, drop older ruby support
|
||||
* New data file location
|
||||
* Added experimental Windows support
|
||||
* Display WordPress metadata on the detected version
|
||||
* Several small fixes
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 156
|
||||
* Total vulnerable plugins: 1324
|
||||
* Total vulnerable themes: 376
|
||||
* Total version vulnerabilities: 1998
|
||||
* Total plugin vulnerabilities: 2057
|
||||
* Total theme vulnerabilities: 449
|
||||
|
||||
## Version 2.9
|
||||
Released: 2015-10-15
|
||||
|
||||
New
|
||||
* GZIP Encoding in updater
|
||||
* Adds --throttle option to throttle requests
|
||||
* Uses new API and local database file structure
|
||||
* Adds last updated and latest version to plugins and themes
|
||||
|
||||
Removed
|
||||
* ArchAssault from README
|
||||
* APIv1 local databases
|
||||
|
||||
General core
|
||||
* Update to Ruby 2.2.3
|
||||
* Use yajl-ruby as JSON parser
|
||||
* New dependancy for Ubuntu 14.04 (libgmp-dev)
|
||||
* Use Travis container based infra and caching
|
||||
|
||||
Fixed issues
|
||||
* Fix #835 - Readme requests to wp root dir
|
||||
* Fix #836 - Critical icon output twice when the site is not running WP
|
||||
* Fix #839 - Terminal-table dependency is broken
|
||||
* Fix #841 - error: undefined method `cells' for #<Array:0x000000029cc2f8>
|
||||
* Fix #852 - GZIP Encoding in updater
|
||||
* Fix #853 - APIv2 integration
|
||||
* Fix #858 - Detection FP
|
||||
* Fix #873 - false positive "site has Must Use Plugins"
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 132
|
||||
* Total vulnerable plugins: 1170
|
||||
* Total vulnerable themes: 368
|
||||
* Total version vulnerabilities: 1476
|
||||
* Total plugin vulnerabilities: 1913
|
||||
* Total theme vulnerabilities: 450
|
||||
|
||||
## Version 2.8
|
||||
Released: 2015-06-22
|
||||
@@ -79,7 +208,7 @@ Fixed issues
|
||||
* Fix #746 - Add a global counter for all active requests to server.
|
||||
* Fix #747 - Add 'security-protection' plugin to wp_login_protection module
|
||||
* Fix #753 - undefined method `round' for "10":String for request or connect timeouts
|
||||
* Fix #760 - typhoeus issue (infinite loop)
|
||||
* Fix #760 - typhoeus issue (infinite loop)
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 89
|
||||
@@ -100,7 +229,7 @@ New
|
||||
* Add Sucuri sponsor to banner
|
||||
* Add protocol to sucuri url in banner
|
||||
* Add response code to proxy error output
|
||||
* Add a statement about mendatory newlines at the end of list
|
||||
* Add a statement about mandatory newlines at the end of list
|
||||
* Give warning if default username 'admin' is still used
|
||||
* License amendment to make it more clear about value added usage
|
||||
|
||||
@@ -456,4 +585,3 @@ Fixed issues
|
||||
|
||||
## Version 2.1
|
||||
Released 2013-3-4
|
||||
|
||||
|
||||
21
CREDITS
21
CREDITS
@@ -1,21 +0,0 @@
|
||||
**CREDITS**
|
||||
|
||||
This file is used to state the individual WPScan Team members (core developers) and give credit to WPScan's other contributors. If you feel your name should be in here email wpscanteam@gmail.com.
|
||||
|
||||
*WPScan Team*
|
||||
|
||||
Erwan.LR - @erwan_lr - (Project Developer)
|
||||
Christian Mehlmauer - @_FireFart_ - (Project Developer)
|
||||
Peter van der Laan - pvdl - (Project Developer)
|
||||
Ryan Dewhurst - @ethicalhack3r (Project Lead)
|
||||
|
||||
*Other Contributors*
|
||||
|
||||
Henri Salo AKA fgeek - Reported lots of vulnerabilities
|
||||
Alip AKA Undead - alip.aswalid at gmail.com
|
||||
michee08 - Reported and gave potential solutions to bugs
|
||||
Callum Pember - Implemented proxy support - callumpember at gmail.com
|
||||
g0tmi1k - Additional timthumb checks + bug reports
|
||||
Melvin Lammerts - Reported a couple of fake vulnerabilities - melvin at 12k.nl
|
||||
Paolo Perego - @thesp0nge - Basic authentication
|
||||
Gianluca Brindisi - @gbrindisi - Ex Project Developer
|
||||
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
FROM ruby:2.5-alpine
|
||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||
|
||||
ARG BUNDLER_ARGS="--jobs=8 --without test"
|
||||
|
||||
# Add a new user
|
||||
RUN adduser -h /wpscan -g WPScan -D wpscan
|
||||
|
||||
# Setup gems
|
||||
RUN echo "gem: --no-ri --no-rdoc" > /etc/gemrc
|
||||
|
||||
COPY Gemfile /wpscan
|
||||
COPY Gemfile.lock /wpscan
|
||||
|
||||
# Runtime dependencies
|
||||
RUN apk add --no-cache libcurl procps && \
|
||||
# build dependencies
|
||||
apk add --no-cache --virtual build-deps alpine-sdk ruby-dev libffi-dev zlib-dev && \
|
||||
bundle install --system --gemfile=/wpscan/Gemfile $BUNDLER_ARGS && \
|
||||
apk del --no-cache build-deps
|
||||
|
||||
# Copy over data & set permissions
|
||||
COPY . /wpscan
|
||||
RUN chown -R wpscan:wpscan /wpscan
|
||||
|
||||
# Switch directory
|
||||
WORKDIR /wpscan
|
||||
|
||||
# Switch users
|
||||
USER wpscan
|
||||
|
||||
# Update WPScan
|
||||
RUN /wpscan/wpscan.rb --update --verbose --no-color
|
||||
|
||||
# Run WPScan
|
||||
ENTRYPOINT ["/wpscan/wpscan.rb"]
|
||||
CMD ["--help"]
|
||||
21
Gemfile
21
Gemfile
@@ -1,15 +1,16 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'typhoeus', '~>0.7.0'
|
||||
gem 'nokogiri'
|
||||
gem 'addressable'
|
||||
gem 'json'
|
||||
gem 'terminal-table'
|
||||
gem 'ruby-progressbar', '>=1.6.0'
|
||||
gem 'addressable', '>=2.5.0'
|
||||
gem 'nokogiri', '>=1.7.0.1'
|
||||
gem 'ruby-progressbar', '>=1.8.1'
|
||||
gem 'rubyzip', '>=1.2.1'
|
||||
gem 'terminal-table', '>=1.6.0'
|
||||
gem 'typhoeus', '>=1.1.2'
|
||||
gem 'yajl-ruby', '>=1.3.0' # Better JSON parser regarding memory usage
|
||||
|
||||
group :test do
|
||||
gem 'webmock', '>=1.17.2'
|
||||
gem 'simplecov'
|
||||
gem 'rspec', '>= 3.3.0'
|
||||
gem 'rspec-its'
|
||||
gem 'webmock', '>=2.3.2'
|
||||
gem 'simplecov', '>=0.13.0'
|
||||
gem 'rspec', '>=3.5.0'
|
||||
gem 'rspec-its', '>=1.2.0'
|
||||
end
|
||||
|
||||
10
LICENSE
10
LICENSE
@@ -1,6 +1,6 @@
|
||||
WPScan Public Source License
|
||||
|
||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2015 WPScan Team.
|
||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2018 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.
|
||||
|
||||
@@ -8,7 +8,7 @@ Cases that include commercialization of WPScan require a commercial, non-free li
|
||||
|
||||
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.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
|
||||
|
||||
@@ -27,7 +27,7 @@ Example cases which do not require a commercial license, and thus fall under the
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
|
||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - wpscanteam@gmail.com.
|
||||
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.
|
||||
|
||||
@@ -68,3 +68,7 @@ To the extent permitted under Law, WPScan is provided under an AS-IS basis. The
|
||||
10. Disclaimer
|
||||
|
||||
Running WPScan against websites without prior mutual consent may be illegal in your country. The WPScan Team accept no liability and are not responsible for any misuse or damage caused by WPScan.
|
||||
|
||||
11. Trademark
|
||||
|
||||
The "wpscan" term is a registered trademark. This License does not grant the use of the "wpscan" trademark or the use of the WPScan logo.
|
||||
|
||||
299
README.md
299
README.md
@@ -1,19 +1,21 @@
|
||||

|
||||
|
||||
|
||||
[](https://travis-ci.org/wpscanteam/wpscan)
|
||||
[](https://codeclimate.com/github/wpscanteam/wpscan)
|
||||
[](https://gemnasium.com/wpscanteam/wpscan)
|
||||
[](https://hub.docker.com/r/wpscanteam/wpscan/)
|
||||
[](https://www.patreon.com/wpscan)
|
||||
|
||||
#### LICENSE
|
||||

|
||||
|
||||
#### WPScan Public Source License
|
||||
# LICENSE
|
||||
|
||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2015 WPScan Team.
|
||||
## WPScan Public Source License
|
||||
|
||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2018 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.
|
||||
|
||||
##### 1. Definitions
|
||||
### 1. Definitions
|
||||
|
||||
1.1 "License" means this document.
|
||||
|
||||
@@ -21,7 +23,7 @@ Cases that include commercialization of WPScan require a commercial, non-free li
|
||||
|
||||
1.3 "WPScan Team" means WPScan’s core developers, an updated list of whom can be found within the CREDITS file.
|
||||
|
||||
##### 2. Commercialization
|
||||
### 2. Commercialization
|
||||
|
||||
A commercial use is one intended for commercial advantage or monetary compensation.
|
||||
|
||||
@@ -38,13 +40,13 @@ Example cases which do not require a commercial license, and thus fall under the
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
|
||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - wpscanteam@gmail.com.
|
||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
||||
|
||||
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
||||
|
||||
Free-use Terms and Conditions;
|
||||
|
||||
##### 3. Redistribution
|
||||
### 3. Redistribution
|
||||
|
||||
Redistribution is permitted under the following conditions:
|
||||
|
||||
@@ -52,35 +54,39 @@ Redistribution is permitted under the following conditions:
|
||||
- Unmodified Copyright notices are provided with WPScan.
|
||||
- Does not conflict with the commercialization clause.
|
||||
|
||||
##### 4. Copying
|
||||
### 4. Copying
|
||||
|
||||
Copying is permitted so long as it does not conflict with the Redistribution clause.
|
||||
|
||||
##### 5. Modification
|
||||
### 5. Modification
|
||||
|
||||
Modification is permitted so long as it does not conflict with the Redistribution clause.
|
||||
|
||||
##### 6. Contributions
|
||||
### 6. Contributions
|
||||
|
||||
Any Contributions assume the Contributor grants the WPScan Team the unlimited, non-exclusive right to reuse, modify and relicense the Contributor's content.
|
||||
|
||||
##### 7. Support
|
||||
### 7. Support
|
||||
|
||||
WPScan is provided under an AS-IS basis and without any support, updates or maintenance. Support, updates and maintenance may be given according to the sole discretion of the WPScan Team.
|
||||
|
||||
##### 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.
|
||||
|
||||
##### 9. Limitation of Liability
|
||||
### 9. Limitation of Liability
|
||||
|
||||
To the extent permitted under Law, WPScan is provided under an AS-IS basis. The WPScan Team shall never, and without any limit, be liable for any damage, cost, expense or any other payment incurred as a result of WPScan's actions, failure, bugs and/or any other interaction between WPScan and end-equipment, computers, other software or any 3rd party, end-equipment, computer or services.
|
||||
|
||||
##### 10. Disclaimer
|
||||
### 10. Disclaimer
|
||||
|
||||
Running WPScan against websites without prior mutual consent may be illegal in your country. The WPScan Team accept no liability and are not responsible for any misuse or damage caused by WPScan.
|
||||
|
||||
#### INSTALL
|
||||
### 11. Trademark
|
||||
|
||||
The "wpscan" term is a registered trademark. This License does not grant the use of the "wpscan" trademark or the use of the WPScan logo.
|
||||
|
||||
# INSTALL
|
||||
|
||||
WPScan comes pre-installed on the following Linux distributions:
|
||||
|
||||
@@ -88,117 +94,106 @@ WPScan comes pre-installed on the following Linux distributions:
|
||||
- [Kali Linux](http://www.kali.org/)
|
||||
- [Pentoo](http://www.pentoo.ch/)
|
||||
- [SamuraiWTF](http://samurai.inguardians.com/)
|
||||
- [ArchAssault](https://archassault.org/)
|
||||
- [BlackArch](http://blackarch.org/)
|
||||
|
||||
Prerequisites:
|
||||
On macOS WPScan is packaged by [Homebrew](https://brew.sh/) as [`wpscan`](http://braumeister.org/formula/wpscan).
|
||||
|
||||
- Ruby >= 1.9.2 - Recommended: 2.2.2
|
||||
Windows is not supported
|
||||
|
||||
We suggest you use our official Docker image from https://hub.docker.com/r/wpscanteam/wpscan/ to avoid installation problems.
|
||||
|
||||
# DOCKER
|
||||
## Install Docker
|
||||
[https://docs.docker.com/engine/installation/](https://docs.docker.com/engine/installation/)
|
||||
|
||||
## Get the image
|
||||
Pull the repo with `docker pull wpscanteam/wpscan`
|
||||
|
||||
## Start WPScan
|
||||
|
||||
```
|
||||
docker run -it --rm wpscanteam/wpscan -u https://yourblog.com [options]
|
||||
```
|
||||
|
||||
For the available Options, please see https://github.com/wpscanteam/wpscan#wpscan-arguments
|
||||
|
||||
If you run the git version of wpscan we included some binstubs in ./bin for easier start of wpscan.
|
||||
|
||||
## Examples
|
||||
|
||||
Mount a local wordlist to the docker container and start a bruteforce attack for user admin
|
||||
|
||||
```
|
||||
docker run -it --rm -v ~/wordlists:/wordlists wpscanteam/wpscan --url https://yourblog.com --wordlist /wordlists/crackstation.txt --username admin
|
||||
```
|
||||
|
||||
(This mounts the host directory `~/wordlists` to the container in the path `/wordlists`)
|
||||
|
||||
Use logfile option
|
||||
```
|
||||
# the file must exist prior to starting the container, otherwise docker will create a directory with the filename
|
||||
touch ~/FILENAME
|
||||
docker run -it --rm -v ~/FILENAME:/wpscan/output.txt wpscanteam/wpscan --url https://yourblog.com --log /wpscan/output.txt
|
||||
```
|
||||
|
||||
Published on https://hub.docker.com/r/wpscanteam/wpscan/
|
||||
|
||||
# Manual install
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Ruby >= 2.1.9 - Recommended: 2.5.1
|
||||
- Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
|
||||
- RubyGems - Recommended: latest
|
||||
- Git
|
||||
|
||||
Windows is not supported.
|
||||
If installed from Github update the code base with ```git pull```. The databases are updated with ```wpscan.rb --update```.
|
||||
### Installing dependencies on Ubuntu
|
||||
|
||||
####Installing on Ubuntu:
|
||||
sudo apt-get install libcurl4-openssl-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential libgmp-dev zlib1g-dev
|
||||
|
||||
Before Ubuntu 14.04:
|
||||
### Installing dependencies on Debian
|
||||
|
||||
sudo apt-get install libcurl4-openssl-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
sudo apt-get install gcc git ruby ruby-dev libcurl4-openssl-dev make zlib1g-dev
|
||||
|
||||
From Ubuntu 14.04:
|
||||
### Installing dependencies on Fedora
|
||||
|
||||
sudo apt-get install libcurl4-openssl-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
sudo dnf install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel patch rpm-build
|
||||
|
||||
####Installing on Debian:
|
||||
|
||||
sudo apt-get install git ruby ruby-dev libcurl4-openssl-dev make
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler
|
||||
bundle install --without test --path vendor/bundle
|
||||
|
||||
####Installing on Fedora:
|
||||
|
||||
sudo yum install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel patch
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
####Installing on Archlinux:
|
||||
### Installing dependencies on Arch Linux
|
||||
|
||||
pacman -Syu ruby
|
||||
pacman -Syu libyaml
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
gem install typhoeus
|
||||
gem install nokogiri
|
||||
|
||||
####Installing on Mac OSX:
|
||||
### Installing dependencies on macOS
|
||||
|
||||
Apple Xcode, Command Line Tools and the libffi are needed (to be able to install the FFI gem), See [http://stackoverflow.com/questions/17775115/cant-setup-ruby-environment-installing-fii-gem-error](http://stackoverflow.com/questions/17775115/cant-setup-ruby-environment-installing-fii-gem-error)
|
||||
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && sudo bundle install --without test
|
||||
## Installing with RVM (recommended when doing a manual install)
|
||||
|
||||
####Installing with RVM:
|
||||
If you are using GNOME Terminal, there are some steps required before executing the commands. See here for more information:
|
||||
https://rvm.io/integration/gnome-terminal#integrating-rvm-with-gnome-terminal
|
||||
|
||||
# Install all prerequisites for your OS (look above)
|
||||
cd ~
|
||||
curl -sSL https://rvm.io/mpapis.asc | gpg --import -
|
||||
curl -sSL https://get.rvm.io | bash -s stable
|
||||
source ~/.rvm/scripts/rvm
|
||||
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
|
||||
rvm install 2.2.2
|
||||
rvm use 2.2.2 --default
|
||||
rvm install 2.5.1
|
||||
rvm use 2.5.1 --default
|
||||
echo "gem: --no-ri --no-rdoc" > ~/.gemrc
|
||||
gem install bundler
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
gem install bundler
|
||||
bundle install --without test
|
||||
|
||||
#### KNOWN ISSUES
|
||||
## Installing manually (not recommended)
|
||||
|
||||
- Typhoeus segmentation fault
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
Update cURL to version => 7.21 (may have to install from source)
|
||||
|
||||
- Proxy not working
|
||||
|
||||
Update cURL to version => 7.21.7 (may have to install from source).
|
||||
|
||||
Installation from sources :
|
||||
|
||||
Grab the sources from http://curl.haxx.se/download.html
|
||||
Decompress the archive
|
||||
Open the folder with the extracted files
|
||||
Run ./configure
|
||||
Run make
|
||||
Run sudo make install
|
||||
Run sudo ldconfig
|
||||
|
||||
|
||||
- cannot load such file -- readline:
|
||||
|
||||
sudo aptitude install libreadline5-dev libncurses5-dev
|
||||
|
||||
Then, open the directory of the readline gem (you have to locate it)
|
||||
|
||||
cd ~/.rvm/src/ruby-1.9.2-p180/ext/readline
|
||||
ruby extconf.rb
|
||||
make
|
||||
make install
|
||||
|
||||
|
||||
See [http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/](http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/) for more details
|
||||
# KNOWN ISSUES
|
||||
|
||||
- no such file to load -- rubygems
|
||||
|
||||
@@ -208,15 +203,12 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
||||
|
||||
See [https://github.com/wpscanteam/wpscan/issues/148](https://github.com/wpscanteam/wpscan/issues/148)
|
||||
|
||||
#### WPSCAN ARGUMENTS
|
||||
# WPSCAN ARGUMENTS
|
||||
|
||||
--update Update the databases.
|
||||
|
||||
--url | -u <target url> The WordPress URL/domain to scan.
|
||||
|
||||
--force | -f Forces WPScan to not check if the remote site is running WordPress.
|
||||
|
||||
--enumerate | -e [option(s)] Enumeration.
|
||||
--update Update the database to the latest version.
|
||||
--url | -u <target url> The WordPress URL/domain to scan.
|
||||
--force | -f Forces WPScan to not check if the remote site is running WordPress.
|
||||
--enumerate | -e [option(s)] Enumeration.
|
||||
option :
|
||||
u usernames from id 1 to 10
|
||||
u[10-20] usernames from id 10 to 20 (you must write [] chars)
|
||||
@@ -230,55 +222,44 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
||||
Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
|
||||
If no option is supplied, the default is "vt,tt,u,vp"
|
||||
|
||||
--exclude-content-based "<regexp or string>" Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied
|
||||
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double)
|
||||
--exclude-content-based "<regexp or string>"
|
||||
Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied.
|
||||
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).
|
||||
--config-file | -c <config file> Use the specified config file, see the example.conf.json.
|
||||
--user-agent | -a <User-Agent> Use the specified User-Agent.
|
||||
--cookie <string> String to read cookies from.
|
||||
--random-agent | -r Use a random User-Agent.
|
||||
--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not
|
||||
--batch Never ask for user input, use the default behaviour.
|
||||
--no-color Do not use colors in the output.
|
||||
--log [filename] Creates a log.txt file with WPScan's output if no filename is supplied. Otherwise the filename is used for logging.
|
||||
--no-banner Prevents the WPScan banner from being displayed.
|
||||
--disable-accept-header Prevents WPScan sending the Accept HTTP header.
|
||||
--disable-referer Prevents setting the Referer header.
|
||||
--disable-tls-checks Disables SSL/TLS certificate verification.
|
||||
--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specify it.
|
||||
Subdirectories are allowed.
|
||||
--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory.
|
||||
If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed
|
||||
--proxy <[protocol://]host:port> Supply a proxy. HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported.
|
||||
If no protocol is given (format host:port), HTTP will be used.
|
||||
--proxy-auth <username:password> Supply the proxy login credentials.
|
||||
--basic-auth <username:password> Set the HTTP Basic authentication.
|
||||
--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.
|
||||
If the "-" option is supplied, the wordlist is expected via STDIN.
|
||||
--username | -U <username> Only brute force the supplied username.
|
||||
--usernames <path-to-file> Only brute force the usernames from the file.
|
||||
--cache-dir <cache-directory> Set the cache directory.
|
||||
--cache-ttl <cache-ttl> Typhoeus cache TTL.
|
||||
--request-timeout <request-timeout> Request Timeout.
|
||||
--connect-timeout <connect-timeout> Connect Timeout.
|
||||
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
|
||||
--throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.
|
||||
--help | -h This help screen.
|
||||
--verbose | -v Verbose output.
|
||||
--version Output the current version and exit.
|
||||
|
||||
--config-file | -c <config file> Use the specified config file, see the example.conf.json
|
||||
|
||||
--user-agent | -a <User-Agent> Use the specified User-Agent
|
||||
|
||||
--random-agent | -r Use a random User-Agent
|
||||
|
||||
--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not
|
||||
|
||||
--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it. Subdirectories are allowed
|
||||
|
||||
--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory. If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed
|
||||
|
||||
--proxy <[protocol://]host:port> Supply a proxy (will override the one from conf/browser.conf.json).
|
||||
HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported. If no protocol is given (format host:port), HTTP will be used
|
||||
|
||||
--proxy-auth <username:password> Supply the proxy login credentials.
|
||||
|
||||
--basic-auth <username:password> Set the HTTP Basic authentication.
|
||||
|
||||
--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.
|
||||
|
||||
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
|
||||
|
||||
--username | -U <username> Only brute force the supplied username.
|
||||
|
||||
--usernames <path-to-file> Only brute force the usernames from the file.
|
||||
|
||||
--cache-ttl <cache-ttl> Typhoeus cache TTL.
|
||||
|
||||
--request-timeout <request-timeout> Request Timeout.
|
||||
|
||||
--connect-timeout <connect-timeout> Connect Timeout.
|
||||
|
||||
--max-threads <max-threads> Maximum Threads.
|
||||
|
||||
--help | -h This help screen.
|
||||
|
||||
--verbose | -v Verbose output.
|
||||
|
||||
--batch Never ask for user input, use the default behavior.
|
||||
|
||||
--no-color Do not use colors in the output.
|
||||
|
||||
--log Save STDOUT to log.txt
|
||||
|
||||
#### WPSCAN EXAMPLES
|
||||
# WPSCAN EXAMPLES
|
||||
|
||||
Do 'non-intrusive' checks...
|
||||
|
||||
@@ -288,6 +269,10 @@ Do wordlist password brute force on enumerated users using 50 threads...
|
||||
|
||||
```ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --threads 50```
|
||||
|
||||
Do wordlist password brute force on enumerated users using STDIN as the wordlist...
|
||||
|
||||
```crunch 5 13 -f charset.lst mixalpha | ruby wpscan.rb --url www.example.com --wordlist -```
|
||||
|
||||
Do wordlist password brute force on the 'admin' username only...
|
||||
|
||||
```ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --username admin```
|
||||
@@ -312,26 +297,22 @@ Debug output...
|
||||
|
||||
```ruby wpscan.rb --url www.example.com --debug-output 2>debug.log```
|
||||
|
||||
#### PROJECT HOME
|
||||
# PROJECT HOME
|
||||
|
||||
[http://www.wpscan.org](http://www.wpscan.org)
|
||||
|
||||
#### VULNERABILITY DATABASE
|
||||
# VULNERABILITY DATABASE
|
||||
|
||||
[https://wpvulndb.com](https://wpvulndb.com)
|
||||
|
||||
#### GIT REPOSITORY
|
||||
# GIT REPOSITORY
|
||||
|
||||
[https://github.com/wpscanteam/wpscan](https://github.com/wpscanteam/wpscan)
|
||||
|
||||
#### ISSUES
|
||||
# ISSUES
|
||||
|
||||
[https://github.com/wpscanteam/wpscan/issues](https://github.com/wpscanteam/wpscan/issues)
|
||||
|
||||
#### DEVELOPER DOCUMENTATION
|
||||
# DEVELOPER DOCUMENTATION
|
||||
|
||||
[http://rdoc.info/github/wpscanteam/wpscan/frames](http://rdoc.info/github/wpscanteam/wpscan/frames)
|
||||
|
||||
#### SPECIAL THANKS
|
||||
|
||||
[RandomStorm](https://www.randomstorm.com)
|
||||
|
||||
21
bin/rspec
Executable file
21
bin/rspec
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
cd $DIR/../
|
||||
# always rebuild and include all GEMs
|
||||
docker build --build-arg "BUNDLER_ARGS=--jobs=8" -t wpscan:rspec .
|
||||
# update all gems (this updates Gemfile.lock on the host)
|
||||
# this also needs some build dependencies
|
||||
docker run --rm -u root -v $DIR/../Gemfile.lock:/wpscan/Gemfile.lock --entrypoint "" wpscan:rspec sh -c 'apk add --no-cache alpine-sdk ruby-dev libffi-dev zlib-dev && bundle update'
|
||||
# rebuild image with latest GEMs
|
||||
docker build --build-arg "BUNDLER_ARGS=--jobs=8" -t wpscan:rspec .
|
||||
# run spec
|
||||
docker run --rm -v $DIR/../:/wpscan --entrypoint "" wpscan:rspec rspec
|
||||
|
||||
12
bin/update_gems
Executable file
12
bin/update_gems
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
cd $DIR/../
|
||||
docker run --rm -v "$DIR/../":/usr/src/app -w /usr/src/app ruby:2.5-alpine /bin/sh -c "gem install bundler; bundle lock --update"
|
||||
14
bin/wpscan
Executable file
14
bin/wpscan
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
cd $DIR/../
|
||||
docker build -q -t wpscan:git .
|
||||
docker run -it --rm wpscan:git "$@"
|
||||
|
||||
16
bin/wpscan-dev
Executable file
16
bin/wpscan-dev
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
cd $DIR/../
|
||||
if [[ -n "$WPSCAN_BUILD" ]]; then
|
||||
docker build -q -t wpscan:git .
|
||||
fi
|
||||
docker run -it --rm -v $DIR/../:/wpscan wpscan:git "$@"
|
||||
|
||||
2
data/.gitignore
vendored
2
data/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
19
dev/stats.rb
Executable file
19
dev/stats.rb
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env ruby
|
||||
# encoding: UTF-8
|
||||
|
||||
require File.expand_path(File.join(__dir__, '..', 'lib', 'wpscan', 'wpscan_helper'))
|
||||
|
||||
wordpress_json = json(WORDPRESSES_FILE)
|
||||
plugins_json = json(PLUGINS_FILE)
|
||||
themes_json = json(THEMES_FILE)
|
||||
|
||||
puts 'WPScan Database Statistics:'
|
||||
puts "* Total tracked wordpresses: #{wordpress_json.count}"
|
||||
puts "* Total tracked plugins: #{plugins_json.count}"
|
||||
puts "* Total tracked themes: #{themes_json.count}"
|
||||
puts "* Total vulnerable wordpresses: #{wordpress_json.select { |item| !wordpress_json[item]['vulnerabilities'].empty? }.count}"
|
||||
puts "* Total vulnerable plugins: #{plugins_json.select { |item| !plugins_json[item]['vulnerabilities'].empty? }.count}"
|
||||
puts "* Total vulnerable themes: #{themes_json.select { |item| !themes_json[item]['vulnerabilities'].empty? }.count}"
|
||||
puts "* Total wordpress vulnerabilities: #{wordpress_json.map {|k,v| v['vulnerabilities'].count}.inject(:+)}"
|
||||
puts "* Total plugin vulnerabilities: #{plugins_json.map {|k,v| v['vulnerabilities'].count}.inject(:+)}"
|
||||
puts "* Total theme vulnerabilities: #{themes_json.map {|k,v| v['vulnerabilities'].count}.inject(:+)}"
|
||||
@@ -2,8 +2,8 @@
|
||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0",
|
||||
|
||||
/* Uncomment the "proxy" line to use the proxy
|
||||
SOCKS proxies (4, 4A, 5) are supported, ie : "proxy": "socks5://127.0.0.1:9000"
|
||||
If you do not specify the protocol, http will be used
|
||||
SOCKS proxies (4, 4A, 5) are supported, ie : "proxy": "socks5://127.0.0.1:9000"
|
||||
If you do not specify the protocol, http will be used
|
||||
*/
|
||||
//"proxy": "127.0.0.1:3128",
|
||||
//"proxy_auth": "username:password",
|
||||
|
||||
@@ -17,14 +17,18 @@ class Browser
|
||||
:proxy_auth,
|
||||
:request_timeout,
|
||||
:connect_timeout,
|
||||
:cookie
|
||||
:cookie,
|
||||
:throttle,
|
||||
:disable_accept_header,
|
||||
:disable_referer,
|
||||
:disable_tls_checks
|
||||
]
|
||||
|
||||
@@instance = nil
|
||||
|
||||
attr_reader :hydra, :cache_dir
|
||||
|
||||
attr_accessor :referer, :cookie
|
||||
attr_accessor :referer, :cookie, :vhost
|
||||
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
@@ -66,16 +70,24 @@ class Browser
|
||||
@@instance = nil
|
||||
end
|
||||
|
||||
# Override for setting the User-Agent
|
||||
# @param [ String ] user_agent
|
||||
def user_agent=(user_agent)
|
||||
Typhoeus::Config.user_agent = user_agent
|
||||
end
|
||||
|
||||
#
|
||||
# sets browser default values
|
||||
#
|
||||
def browser_defaults
|
||||
@max_threads = 20
|
||||
# 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
|
||||
@cache_ttl = 600
|
||||
Typhoeus::Config.user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
|
||||
@max_threads = 20
|
||||
# 10 minutes, at this time the cache is cleaned before each scan.
|
||||
# If this value is set to 0, the cache will be disabled
|
||||
@cache_ttl = 600
|
||||
@request_timeout = 60 # 60s
|
||||
@connect_timeout = 10 # 10s
|
||||
@user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
|
||||
@throttle = 0
|
||||
end
|
||||
|
||||
#
|
||||
@@ -86,7 +98,6 @@ class Browser
|
||||
#
|
||||
# @return [ void ]
|
||||
def load_config(config_file = nil)
|
||||
|
||||
if File.symlink?(config_file)
|
||||
raise '[ERROR] Config file is a symlink.'
|
||||
else
|
||||
@@ -99,7 +110,6 @@ class Browser
|
||||
self.send(:"#{option_name}=", data[option_name])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# @param [ String ] url
|
||||
@@ -114,18 +124,9 @@ class Browser
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def merge_request_params(params = {})
|
||||
params = Browser.append_params_header_field(
|
||||
params,
|
||||
'User-Agent',
|
||||
@user_agent
|
||||
)
|
||||
|
||||
if @proxy
|
||||
params = params.merge(proxy: @proxy)
|
||||
|
||||
if @proxy_auth
|
||||
params = params.merge(proxyauth: @proxy_auth)
|
||||
end
|
||||
params.merge!(proxy: @proxy)
|
||||
params.merge!(proxyuserpwd: @proxy_auth) if @proxy_auth
|
||||
end
|
||||
|
||||
if @basic_auth
|
||||
@@ -136,23 +137,37 @@ class Browser
|
||||
)
|
||||
end
|
||||
|
||||
if vhost
|
||||
params = Browser.append_params_header_field(
|
||||
params,
|
||||
'Host',
|
||||
vhost
|
||||
)
|
||||
end
|
||||
|
||||
params.merge!(referer: referer)
|
||||
params.merge!(timeout: @request_timeout) if @request_timeout
|
||||
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout
|
||||
params.merge!(timeout: @request_timeout) if @request_timeout && !params.key?(:timeout)
|
||||
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout && !params.key?(:connecttimeout)
|
||||
|
||||
# Used to enable the cache system if :cache_ttl > 0
|
||||
params.merge!(cache_ttl: @cache_ttl) unless params.has_key?(:cache_ttl)
|
||||
params.merge!(cache_ttl: @cache_ttl) unless params.key?(:cache_ttl)
|
||||
|
||||
# Prevent infinite self redirection
|
||||
params.merge!(maxredirs: 3) unless params.has_key?(:maxredirs)
|
||||
params.merge!(maxredirs: 3) unless params.key?(:maxredirs)
|
||||
|
||||
# Disable SSL-Certificate checks
|
||||
params.merge!(ssl_verifypeer: false)
|
||||
params.merge!(ssl_verifyhost: 0)
|
||||
if @disable_tls_checks
|
||||
# Cert validity check
|
||||
params.merge!(ssl_verifypeer: 0) unless params.key?(:ssl_verifypeer)
|
||||
# Cert hostname check
|
||||
params.merge!(ssl_verifyhost: 0) unless params.key?(:ssl_verifyhost)
|
||||
end
|
||||
|
||||
params.merge!(cookiejar: @cache_dir + '/cookie-jar')
|
||||
params.merge!(cookiefile: @cache_dir + '/cookie-jar')
|
||||
params.merge!(cookie: @cookie) if @cookie
|
||||
params = Browser.remove_params_header_field(params, 'Accept') if @disable_accept_header
|
||||
params = Browser.remove_params_header_field(params, 'Referer') if @disable_referer
|
||||
|
||||
params
|
||||
end
|
||||
@@ -173,4 +188,17 @@ class Browser
|
||||
params
|
||||
end
|
||||
|
||||
# @param [ Hash ] params
|
||||
# @param [ String ] field
|
||||
# @param [ Mixed ] field_value
|
||||
#
|
||||
# @return [ Array ]
|
||||
def self.remove_params_header_field(params = {}, field)
|
||||
if !params.has_key?(:headers)
|
||||
params = params.merge(:headers => { field => nil })
|
||||
elsif !params[:headers].has_key?(field)
|
||||
params[:headers][field] = nil
|
||||
end
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
class Browser
|
||||
module Options
|
||||
|
||||
attr_accessor :cache_ttl, :request_timeout, :connect_timeout
|
||||
attr_reader :basic_auth, :proxy, :proxy_auth
|
||||
attr_writer :user_agent
|
||||
attr_accessor :request_timeout, :connect_timeout, :user_agent, :disable_accept_header, :disable_referer, :disable_tls_checks
|
||||
attr_reader :basic_auth, :cache_ttl, :proxy, :proxy_auth, :throttle
|
||||
|
||||
# Sets the Basic Authentification credentials
|
||||
# Accepted format:
|
||||
@@ -21,10 +20,14 @@ class Browser
|
||||
elsif auth =~ /\ABasic [a-zA-Z0-9=]+\z/
|
||||
@basic_auth = auth
|
||||
else
|
||||
raise 'Invalid basic authentication format, "login:password" or "Basic base_64_encoded" expected'
|
||||
raise "Invalid basic authentication format, \"login:password\" or \"Basic base_64_encoded\" expected. Your input: #{auth}"
|
||||
end
|
||||
end
|
||||
|
||||
def cache_ttl=(ttl)
|
||||
@cache_ttl = ttl.to_i
|
||||
end
|
||||
|
||||
# @return [ Integer ]
|
||||
def max_threads
|
||||
@max_threads || 1
|
||||
@@ -93,6 +96,11 @@ class Browser
|
||||
@connect_timeout = timeout.to_i
|
||||
end
|
||||
|
||||
# @param [ String, Integer ] throttle
|
||||
def throttle=(throttle)
|
||||
@throttle = throttle.to_i.abs / 1000.0
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def invalid_proxy_auth_format
|
||||
@@ -110,6 +118,5 @@ class Browser
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,11 +23,13 @@ class CacheFileStore
|
||||
@storage_path = File.expand_path(File.join(storage_path, storage_dir))
|
||||
@serializer = serializer
|
||||
|
||||
# File.directory? for ruby <= 1.9 otherwise,
|
||||
# it makes more sense to do Dir.exist? :/
|
||||
unless File.directory?(@storage_path)
|
||||
unless Dir.exist?(@storage_path)
|
||||
FileUtils.mkdir_p(@storage_path)
|
||||
end
|
||||
|
||||
unless Pathname.new(@storage_path).writable?
|
||||
fail "#{@storage_path} is not writable"
|
||||
end
|
||||
end
|
||||
|
||||
def clean
|
||||
@@ -51,7 +53,7 @@ class CacheFileStore
|
||||
end
|
||||
|
||||
def write_entry(key, data_to_store, cache_ttl)
|
||||
if cache_ttl > 0
|
||||
if cache_ttl && cache_ttl > 0
|
||||
File.open(get_entry_file_path(key), 'w') do |f|
|
||||
begin
|
||||
f.write(@serializer.dump(data_to_store))
|
||||
|
||||
1
lib/common/collections/wp_items.rb
Executable file → Normal file
1
lib/common/collections/wp_items.rb
Executable file → Normal file
@@ -67,6 +67,7 @@ class WpItems < Array
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @return [ Class ]
|
||||
def item_class
|
||||
Object.const_get(self.class.to_s.gsub(/.$/, ''))
|
||||
|
||||
42
lib/common/collections/wp_items/detectable.rb
Executable file → Normal file
42
lib/common/collections/wp_items/detectable.rb
Executable file → Normal file
@@ -32,11 +32,7 @@ class WpItems < Array
|
||||
progress_bar.progress += 1 if options[:show_progression]
|
||||
|
||||
if target_item.exists?(exist_options, response)
|
||||
unless results.include?(target_item)
|
||||
if !options[:only_vulnerable] || options[:only_vulnerable] && target_item.vulnerable?
|
||||
results << target_item
|
||||
end
|
||||
end
|
||||
results << target_item unless results.include?(target_item)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,7 +49,7 @@ class WpItems < Array
|
||||
# run the remaining requests
|
||||
hydra.run
|
||||
|
||||
results.select!(&:vulnerable?) if options[:only_vulnerable]
|
||||
results.select!(&:vulnerable?) if options[:type] == :vulnerable
|
||||
results.sort!
|
||||
|
||||
results # can't just return results.sort as it would return an array, and we want a WpItems
|
||||
@@ -99,6 +95,10 @@ class WpItems < Array
|
||||
code = tag.text.to_s
|
||||
next if code.empty?
|
||||
|
||||
if !code.valid_encoding?
|
||||
code = code.encode('UTF-16be', :invalid => :replace, :replace => '?').encode('UTF-8')
|
||||
end
|
||||
|
||||
code.scan(code_pattern(wp_target)).flatten.uniq.each do |item_name|
|
||||
names << item_name
|
||||
end
|
||||
@@ -120,7 +120,7 @@ class WpItems < Array
|
||||
wp_content_dir = wp_target.wp_content_dir
|
||||
wp_content_url = wp_target.uri.merge(wp_content_dir).to_s
|
||||
|
||||
url = /#{wp_content_url.gsub(%r{\A(?:http|https)}, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
url = wp_content_url.gsub(%r{\A(?:http|https)://}, '(?:https?:)?//').gsub('/', '\\\\\?\/')
|
||||
content_dir = %r{(?:#{url}|\\?\/\\?\/?#{wp_content_dir})}i
|
||||
|
||||
%r{#{content_dir}\\?/#{type}\\?/}
|
||||
@@ -155,15 +155,7 @@ class WpItems < Array
|
||||
item_class = self.item_class
|
||||
vulns_file = self.vulns_file
|
||||
|
||||
targets = vulnerable_targets_items(wp_target, item_class, vulns_file)
|
||||
|
||||
unless options[:only_vulnerable]
|
||||
unless options[:file]
|
||||
raise 'A file must be supplied'
|
||||
end
|
||||
|
||||
targets += targets_items_from_file(options[:file], wp_target, item_class, vulns_file)
|
||||
end
|
||||
targets = target_items_from_type(wp_target, item_class, vulns_file, options[:type])
|
||||
|
||||
targets.uniq! { |t| t.name }
|
||||
targets.sort_by { rand }
|
||||
@@ -174,14 +166,25 @@ class WpItems < Array
|
||||
# @param [ String ] vulns_file
|
||||
#
|
||||
# @return [ Array<WpItem> ]
|
||||
def vulnerable_targets_items(wp_target, item_class, vulns_file)
|
||||
def target_items_from_type(wp_target, item_class, vulns_file, type)
|
||||
targets = []
|
||||
json = json(vulns_file)
|
||||
|
||||
[*json].each do |item|
|
||||
case type
|
||||
when :vulnerable
|
||||
items = json.select { |item| !json[item]['vulnerabilities'].empty? }.keys
|
||||
when :popular
|
||||
items = json.select { |item| json[item]['popular'] == true }.keys
|
||||
when :all
|
||||
items = json.keys
|
||||
else
|
||||
raise "Unknown type #{type}"
|
||||
end
|
||||
|
||||
items.each do |item|
|
||||
targets << create_item(
|
||||
item_class,
|
||||
item.keys.inject,
|
||||
item,
|
||||
wp_target,
|
||||
vulns_file
|
||||
)
|
||||
@@ -233,6 +236,5 @@ class WpItems < Array
|
||||
def item_class
|
||||
Object.const_get(self.to_s.gsub(/.$/, ''))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
16
lib/common/collections/wp_plugins.rb
Executable file → Normal file
16
lib/common/collections/wp_plugins.rb
Executable file → Normal file
@@ -1,8 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_plugins/detectable'
|
||||
|
||||
class WpPlugins < WpItems
|
||||
extend WpPlugins::Detectable
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_plugins/detectable'
|
||||
|
||||
class WpPlugins < WpItems
|
||||
extend WpPlugins::Detectable
|
||||
|
||||
end
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
|
||||
class WpPlugins < WpItems
|
||||
module Detectable
|
||||
|
||||
# @return [ String ]
|
||||
def vulns_file
|
||||
PLUGINS_VULNS_FILE
|
||||
PLUGINS_FILE
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
# def item_xpath
|
||||
# '//plugin'
|
||||
# end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
@@ -68,10 +62,14 @@ class WpPlugins < WpItems
|
||||
wp_plugins.add('all-in-one-seo-pack', version: $1)
|
||||
end
|
||||
|
||||
if body =~ /<!-- This site is optimized with the Yoast WordPress SEO plugin v([^\s]+) -/i
|
||||
if body =~ /<!-- This site is optimized with the Yoast (?:WordPress )?SEO plugin v([^\s]+) -/i
|
||||
wp_plugins.add('wordpress-seo', version: $1)
|
||||
end
|
||||
|
||||
if body =~ /<!-- Google Universal Analytics for WordPress v([^\s]+) -/i
|
||||
wp_plugins.add('google-universal-analytics', version: $1)
|
||||
end
|
||||
|
||||
wp_plugins
|
||||
end
|
||||
|
||||
|
||||
16
lib/common/collections/wp_themes.rb
Executable file → Normal file
16
lib/common/collections/wp_themes.rb
Executable file → Normal file
@@ -1,8 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_themes/detectable'
|
||||
|
||||
class WpThemes < WpItems
|
||||
extend WpThemes::Detectable
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_themes/detectable'
|
||||
|
||||
class WpThemes < WpItems
|
||||
extend WpThemes::Detectable
|
||||
|
||||
end
|
||||
|
||||
@@ -5,13 +5,7 @@ class WpThemes < WpItems
|
||||
|
||||
# @return [ String ]
|
||||
def vulns_file
|
||||
THEMES_VULNS_FILE
|
||||
THEMES_FILE
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
# def item_xpath
|
||||
# '//theme'
|
||||
# end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
16
lib/common/collections/wp_timthumbs.rb
Executable file → Normal file
16
lib/common/collections/wp_timthumbs.rb
Executable file → Normal file
@@ -1,8 +1,8 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_timthumbs/detectable'
|
||||
|
||||
class WpTimthumbs < WpItems
|
||||
extend WpTimthumbs::Detectable
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_timthumbs/detectable'
|
||||
|
||||
class WpTimthumbs < WpItems
|
||||
extend WpTimthumbs::Detectable
|
||||
|
||||
end
|
||||
|
||||
@@ -41,7 +41,7 @@ class WpTimthumbs < WpItems
|
||||
|
||||
%w{
|
||||
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
|
||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php thumb.php
|
||||
}.each do |path|
|
||||
wp_timthumb.path = "$wp-content$/themes/#{theme_name}/#{path}"
|
||||
|
||||
|
||||
22
lib/common/collections/wp_users.rb
Executable file → Normal file
22
lib/common/collections/wp_users.rb
Executable file → Normal file
@@ -1,11 +1,11 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_users/detectable'
|
||||
require 'common/collections/wp_users/output'
|
||||
require 'common/collections/wp_users/brute_forcable'
|
||||
|
||||
class WpUsers < WpItems
|
||||
extend WpUsers::Detectable
|
||||
include WpUsers::Output
|
||||
include WpUsers::BruteForcable
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/collections/wp_users/detectable'
|
||||
require 'common/collections/wp_users/output'
|
||||
require 'common/collections/wp_users/brute_forcable'
|
||||
|
||||
class WpUsers < WpItems
|
||||
extend WpUsers::Detectable
|
||||
include WpUsers::Output
|
||||
include WpUsers::BruteForcable
|
||||
end
|
||||
|
||||
68
lib/common/collections/wp_users/detectable.rb
Executable file → Normal file
68
lib/common/collections/wp_users/detectable.rb
Executable file → Normal file
@@ -1,34 +1,34 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUsers < WpItems
|
||||
module Detectable
|
||||
|
||||
# @return [ Hash ]
|
||||
def request_params; {} end
|
||||
|
||||
# No passive detection
|
||||
#
|
||||
# @return [ WpUsers ]
|
||||
def passive_detection(wp_target, options = {})
|
||||
new
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
# @option options [ Range ] :range ((1..10))
|
||||
#
|
||||
# @return [ Array<WpUser> ]
|
||||
def targets_items(wp_target, options = {})
|
||||
range = options[:range] || (1..10)
|
||||
targets = []
|
||||
|
||||
range.each do |user_id|
|
||||
targets << WpUser.new(wp_target.uri, id: user_id)
|
||||
end
|
||||
targets
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUsers < WpItems
|
||||
module Detectable
|
||||
|
||||
# @return [ Hash ]
|
||||
def request_params; {} end
|
||||
|
||||
# No passive detection
|
||||
#
|
||||
# @return [ WpUsers ]
|
||||
def passive_detection(wp_target, options = {})
|
||||
new
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
# @option options [ Range ] :range ((1..10))
|
||||
#
|
||||
# @return [ Array<WpUser> ]
|
||||
def targets_items(wp_target, options = {})
|
||||
range = options[:range] || (1..10)
|
||||
targets = []
|
||||
|
||||
range.each do |user_id|
|
||||
targets << WpUser.new(wp_target.uri, id: user_id)
|
||||
end
|
||||
targets
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,7 @@ class WpUsers < WpItems
|
||||
# @return [ void ]
|
||||
def output(options = {})
|
||||
rows = []
|
||||
headings = ['Id', 'Login', 'Name']
|
||||
headings = ['ID', 'Login', 'Name']
|
||||
headings << 'Password' if options[:show_password]
|
||||
|
||||
remove_junk_from_display_names
|
||||
|
||||
@@ -1,39 +1,34 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
LIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
||||
ROOT_DIR = File.expand_path(File.join(LIB_DIR, '..')) # expand_path is used to get "wpscan/" instead of "wpscan/lib/../"
|
||||
DATA_DIR = File.join(ROOT_DIR, 'data')
|
||||
CONF_DIR = File.join(ROOT_DIR, 'conf')
|
||||
CACHE_DIR = File.join(ROOT_DIR, 'cache')
|
||||
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan')
|
||||
UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater')
|
||||
COMMON_LIB_DIR = File.join(LIB_DIR, 'common')
|
||||
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models')
|
||||
COLLECTIONS_LIB_DIR = File.join(COMMON_LIB_DIR, 'collections')
|
||||
# Location directories
|
||||
LIB_DIR = File.expand_path(File.join(__dir__, '..')) # wpscan/lib/
|
||||
ROOT_DIR = File.expand_path(File.join(LIB_DIR, '..')) # wpscan/ - expand_path is used to get "wpscan/" instead of "wpscan/lib/../"
|
||||
USER_DIR = File.expand_path(Dir.home) # ~/
|
||||
|
||||
LOG_FILE = File.join(ROOT_DIR, 'log.txt')
|
||||
# Core WPScan directories
|
||||
CACHE_DIR = File.join(USER_DIR, '.wpscan/cache') # ~/.wpscan/cache/
|
||||
DATA_DIR = File.join(USER_DIR, '.wpscan/data') # ~/.wpscan/data/
|
||||
CONF_DIR = File.join(USER_DIR, '.wpscan/conf') # ~/.wpscan/conf/ - Not used ATM (only ref via ./spec/ for travis)
|
||||
COMMON_LIB_DIR = File.join(LIB_DIR, 'common') # wpscan/lib/common/
|
||||
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan') # wpscan/lib/wpscan/
|
||||
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models') # wpscan/lib/common/models/
|
||||
|
||||
# Plugins directories
|
||||
COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins')
|
||||
WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM
|
||||
# Core WPScan files
|
||||
DEFAULT_LOG_FILE = File.join(USER_DIR, '.wpscan/log.txt') # ~/.wpscan/log.txt
|
||||
DATA_FILE = File.join(ROOT_DIR, 'data.zip') # wpscan/data.zip
|
||||
|
||||
# Data files
|
||||
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.txt')
|
||||
PLUGINS_FULL_FILE = File.join(DATA_DIR, 'plugins_full.txt')
|
||||
PLUGINS_VULNS_FILE = File.join(DATA_DIR, 'plugin_vulns.json')
|
||||
THEMES_FILE = File.join(DATA_DIR, 'themes.txt')
|
||||
THEMES_FULL_FILE = File.join(DATA_DIR, 'themes_full.txt')
|
||||
THEMES_VULNS_FILE = File.join(DATA_DIR, 'theme_vulns.json')
|
||||
WP_VULNS_FILE = File.join(DATA_DIR, 'wp_vulns.json')
|
||||
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml')
|
||||
LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml')
|
||||
# VULNS_XSD = File.join(DATA_DIR, 'vuln.xsd')
|
||||
WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd')
|
||||
LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd')
|
||||
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
|
||||
LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update')
|
||||
# WPScan Data files (data.zip)
|
||||
LAST_UPDATE_FILE = File.join(DATA_DIR, '.last_update') # ~/.wpscan/data/.last_update
|
||||
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.json') # ~/.wpscan/data/plugins.json
|
||||
THEMES_FILE = File.join(DATA_DIR, 'themes.json') # ~/.wpscan/data/themes.json
|
||||
TIMTHUMBS_FILE = File.join(DATA_DIR, 'timthumbs.txt') # ~/.wpscan/data/timthumbs.txt
|
||||
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt') # ~/.wpscan/data/user-agents.txt
|
||||
WORDPRESSES_FILE = File.join(DATA_DIR, 'wordpresses.json') # ~/.wpscan/data/wordpresses.json
|
||||
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml') # ~/.wpscan/data/wp_versions.xml
|
||||
|
||||
WPSCAN_VERSION = '2.8'
|
||||
MIN_RUBY_VERSION = '2.1.9'
|
||||
|
||||
WPSCAN_VERSION = '2.9.5-dev'
|
||||
|
||||
$LOAD_PATH.unshift(LIB_DIR)
|
||||
$LOAD_PATH.unshift(WPSCAN_LIB_DIR)
|
||||
@@ -47,16 +42,26 @@ def kali_linux?
|
||||
end
|
||||
end
|
||||
|
||||
# Determins if installed on Windows OS
|
||||
def windows?
|
||||
Gem.win_platform?
|
||||
end
|
||||
|
||||
require 'environment'
|
||||
require 'zip'
|
||||
|
||||
def escape_glob(s)
|
||||
s.gsub(/[\\\{\}\[\]\*\?]/) { |x| '\\' + x }
|
||||
end
|
||||
|
||||
# TODO : add an exclude pattern ?
|
||||
def require_files_from_directory(absolute_dir_path, files_pattern = '*.rb')
|
||||
files = Dir[File.join(absolute_dir_path, files_pattern)]
|
||||
files = Dir[File.join(escape_glob(absolute_dir_path), files_pattern)]
|
||||
|
||||
# Files in the root dir are loaded first, then those in the subdirectories
|
||||
files.sort_by { |file| [file.count('/'), file] }.each do |f|
|
||||
f = File.expand_path(f)
|
||||
#puts "require #{f}" # Used for debug
|
||||
# puts "require #{f}" # Used for debug
|
||||
require f
|
||||
end
|
||||
end
|
||||
@@ -72,13 +77,39 @@ def add_trailing_slash(url)
|
||||
url =~ /\/$/ ? url : "#{url}/"
|
||||
end
|
||||
|
||||
def missing_db_file?
|
||||
def missing_db_files?
|
||||
DbUpdater::FILES.each do |db_file|
|
||||
return true unless File.exist?(File.join(DATA_DIR, db_file))
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
# Find data.zip?
|
||||
def has_db_zip?
|
||||
return File.exist?(DATA_FILE)
|
||||
end
|
||||
|
||||
# Extract data.zip
|
||||
def extract_db_zip
|
||||
# Create data folder
|
||||
FileUtils.mkdir_p(DATA_DIR)
|
||||
|
||||
Zip::File.open(DATA_FILE) do |zip_file|
|
||||
zip_file.each do |f|
|
||||
# Feedback to the user
|
||||
#puts "[+] Extracting: #{File.basename(f.name)}"
|
||||
f_path = File.join(DATA_DIR, File.basename(f.name))
|
||||
|
||||
# Delete if already there
|
||||
#puts "[+] Deleting: #{File.basename(f.name)}" if File.exist?(f_path)
|
||||
FileUtils.rm(f_path) if File.exist?(f_path)
|
||||
|
||||
# Extract
|
||||
zip_file.extract(f, f_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def last_update
|
||||
date = nil
|
||||
if File.exists?(LAST_UPDATE_FILE)
|
||||
@@ -88,9 +119,12 @@ def last_update
|
||||
date
|
||||
end
|
||||
|
||||
# Was it 5 days ago?
|
||||
def update_required?
|
||||
date = last_update
|
||||
(true if date.nil?) or (date < 5.days.ago)
|
||||
day_seconds = 24 * 60 * 60
|
||||
five_days_ago = Time.now - (5 * day_seconds)
|
||||
(true if date.nil?) or (date < five_days_ago)
|
||||
end
|
||||
|
||||
# Define colors
|
||||
@@ -145,7 +179,7 @@ def banner
|
||||
puts '_______________________________________________________________'
|
||||
puts ' __ _______ _____ '
|
||||
puts ' \\ \\ / / __ \\ / ____| '
|
||||
puts ' \\ \\ /\\ / /| |__) | (___ ___ __ _ _ __ '
|
||||
puts ' \\ \\ /\\ / /| |__) | (___ ___ __ _ _ __ ®'
|
||||
puts ' \\ \\/ \\/ / | ___/ \\___ \\ / __|/ _` | \'_ \\ '
|
||||
puts ' \\ /\\ / | | ____) | (__| (_| | | | |'
|
||||
puts ' \\/ \\/ |_| |_____/ \\___|\\__,_|_| |_|'
|
||||
@@ -153,7 +187,7 @@ def banner
|
||||
puts ' WordPress Security Scanner by the WPScan Team '
|
||||
puts " Version #{WPSCAN_VERSION}"
|
||||
puts ' Sponsored by Sucuri - https://sucuri.net'
|
||||
puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, pvdl, @_FireFart_'
|
||||
puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_'
|
||||
puts '_______________________________________________________________'
|
||||
puts
|
||||
end
|
||||
@@ -224,7 +258,11 @@ end
|
||||
#
|
||||
# @return [ Integer ] The number of lines in the given file
|
||||
def count_file_lines(file)
|
||||
`wc -l #{file.shellescape}`.split[0].to_i
|
||||
if windows?
|
||||
`findstr /R /N "^" #{file.shellescape} | find /C ":"`.split[0].to_i
|
||||
else
|
||||
`wc -l #{file.shellescape}`.split[0].to_i
|
||||
end
|
||||
end
|
||||
|
||||
# Truncates a string to a specific length and adds ... at the end
|
||||
@@ -241,14 +279,26 @@ end
|
||||
# @return [ String ] A random user-agent from data/user-agents.txt
|
||||
def get_random_user_agent
|
||||
user_agents = []
|
||||
|
||||
# If we can't access the file, die
|
||||
raise('[ERROR] Missing user-agent data. Please re-run with just --update.') unless File.exist?(USER_AGENTS_FILE)
|
||||
|
||||
# Read in file
|
||||
f = File.open(USER_AGENTS_FILE, 'r')
|
||||
|
||||
# Read every line in the file
|
||||
f.each_line do |line|
|
||||
# ignore comments
|
||||
# Remove any End of Line issues, and leading/trailing spaces
|
||||
line = line.strip.chomp
|
||||
# Ignore empty files and comments
|
||||
next if line.empty? or line =~ /^\s*(#|\/\/)/
|
||||
# Add to array
|
||||
user_agents << line.strip
|
||||
end
|
||||
# Close file handler
|
||||
f.close
|
||||
# return ransom user-agent
|
||||
|
||||
# Return random user-agent
|
||||
user_agents.sample
|
||||
end
|
||||
|
||||
@@ -258,3 +308,25 @@ end
|
||||
def directory_listing_enabled?(url)
|
||||
Browser.get(url.to_s).body[%r{<title>Index of}] ? true : false
|
||||
end
|
||||
|
||||
def url_encode(str)
|
||||
CGI.escape(str).gsub("+", "%20")
|
||||
end
|
||||
|
||||
# Check valid JSON?
|
||||
def valid_json?(json)
|
||||
JSON.parse(json)
|
||||
return true
|
||||
rescue JSON::ParserError => e
|
||||
return false
|
||||
end
|
||||
|
||||
# Get the HTTP response code
|
||||
def get_http_status(url)
|
||||
Browser.get(url.to_s).code
|
||||
end
|
||||
|
||||
# Check to see if we need a "s"
|
||||
def grammar_s(size)
|
||||
size.to_i >= 2 ? "s" : ""
|
||||
end
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
class DbUpdater
|
||||
FILES = %w(
|
||||
local_vulnerable_files.xml local_vulnerable_files.xsd
|
||||
plugins_full.txt plugins.txt themes_full.txt themes.txt
|
||||
timthumbs.txt user-agents.txt wp_versions.xml wp_versions.xsd
|
||||
plugin_vulns.json theme_vulns.json wp_vulns.json LICENSE
|
||||
wordpresses.json plugins.json themes.json LICENSE
|
||||
)
|
||||
|
||||
attr_reader :repo_directory
|
||||
@@ -14,21 +13,29 @@ class DbUpdater
|
||||
def initialize(repo_directory)
|
||||
@repo_directory = repo_directory
|
||||
|
||||
fail "#{repo_directory} is not writable" unless \
|
||||
Pathname.new(repo_directory).writable?
|
||||
unless Dir.exist?(@repo_directory)
|
||||
FileUtils.mkdir_p(@repo_directory)
|
||||
end
|
||||
|
||||
unless Pathname.new(@repo_directory).writable?
|
||||
fail "#{@repo_directory} is not writable"
|
||||
end
|
||||
end
|
||||
|
||||
# @return [ Hash ] The params for Typhoeus::Request
|
||||
def request_params
|
||||
{
|
||||
ssl_verifyhost: 2,
|
||||
ssl_verifypeer: true
|
||||
ssl_verifypeer: true,
|
||||
accept_encoding: 'gzip, deflate',
|
||||
timeout: 300,
|
||||
connecttimeout: 20
|
||||
}
|
||||
end
|
||||
|
||||
# @return [ String ] The raw file URL associated with the given filename
|
||||
def remote_file_url(filename)
|
||||
"https://wpvulndb.com/data/#{filename}"
|
||||
"https://data.wpscan.org/#{filename}"
|
||||
end
|
||||
|
||||
# @return [ String ] The checksum of the associated remote filename
|
||||
@@ -81,7 +88,7 @@ class DbUpdater
|
||||
def update(verbose = false)
|
||||
FILES.each do |filename|
|
||||
begin
|
||||
puts "[+] Checking #{filename}" if verbose
|
||||
puts "[+] Checking: #{filename}" if verbose
|
||||
db_checksum = remote_file_checksum(filename)
|
||||
|
||||
# Checking if the file needs to be updated
|
||||
@@ -93,13 +100,13 @@ class DbUpdater
|
||||
puts ' [i] Needs to be updated' if verbose
|
||||
create_backup(filename)
|
||||
puts ' [i] Backup Created' if verbose
|
||||
puts ' [i] Downloading new file' if verbose
|
||||
puts " [i] Downloading new file: #{remote_file_url(filename)}" if verbose
|
||||
dl_checksum = download(filename)
|
||||
puts " [i] Downloaded File Checksum: #{dl_checksum}" if verbose
|
||||
puts " [i] Database File Checksum : #{db_checksum}" if verbose
|
||||
|
||||
unless dl_checksum == db_checksum
|
||||
fail "#{filename}: checksums do not match"
|
||||
raise ChecksumError.new(File.read(local_file_path(filename))), "#{filename}: checksums do not match (local: #{dl_checksum} remote: #{db_checksum})"
|
||||
end
|
||||
rescue => e
|
||||
puts ' [i] Restoring Backup due to error' if verbose
|
||||
|
||||
@@ -31,3 +31,11 @@ class DownloadError < HttpError
|
||||
"Unable to get #{failure_details}"
|
||||
end
|
||||
end
|
||||
|
||||
class ChecksumError < StandardError
|
||||
attr_reader :file
|
||||
|
||||
def initialize(file)
|
||||
@file = file
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,35 +1,5 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
# Since ruby 1.9.2, URI::escape is obsolete
|
||||
# See http://rosettacode.org/wiki/URL_encoding#Ruby and http://www.ruby-forum.com/topic/207489
|
||||
if RUBY_VERSION >= '1.9.2'
|
||||
module URI
|
||||
extend self
|
||||
|
||||
def escape(str)
|
||||
URI::Parser.new.escape(str)
|
||||
end
|
||||
alias :encode :escape
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if RUBY_VERSION < '1.9'
|
||||
class Array
|
||||
# Fix for grep with symbols in ruby <= 1.8.7
|
||||
def _grep_(regexp)
|
||||
matches = []
|
||||
self.each do |value|
|
||||
value = value.to_s
|
||||
matches << value if value.match(regexp)
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
alias_method :grep, :_grep_
|
||||
end
|
||||
end
|
||||
|
||||
# This is used in WpItem::Existable
|
||||
module Typhoeus
|
||||
class Response
|
||||
@@ -51,71 +21,17 @@ end
|
||||
def puts(o = '')
|
||||
if $log && o.respond_to?(:gsub)
|
||||
temp = o.gsub(/\e\[\d+m/, '') # remove color for logging
|
||||
File.open(LOG_FILE, 'a+') { |f| f.puts(temp) }
|
||||
File.open($log, 'a+') { |f| f.puts(temp) }
|
||||
end
|
||||
|
||||
super(o)
|
||||
end
|
||||
|
||||
module Terminal
|
||||
class Table
|
||||
def render
|
||||
separator = Separator.new(self)
|
||||
buffer = [separator]
|
||||
unless @title.nil?
|
||||
buffer << Row.new(self, [title_cell_options])
|
||||
buffer << separator
|
||||
end
|
||||
unless @headings.cells.empty?
|
||||
buffer << @headings
|
||||
buffer << separator
|
||||
end
|
||||
buffer += @rows
|
||||
buffer << separator
|
||||
buffer.map { |r| style.margin_left + r.render }.join("\n")
|
||||
end
|
||||
alias :to_s :render
|
||||
|
||||
class Style
|
||||
@@defaults = {
|
||||
:border_x => '-', :border_y => '|', :border_i => '+',
|
||||
:padding_left => 1, :padding_right => 1,
|
||||
:margin_left => '',
|
||||
:width => nil, :alignment => nil
|
||||
}
|
||||
|
||||
attr_accessor :margin_left
|
||||
attr_accessor :border_x
|
||||
attr_accessor :border_y
|
||||
attr_accessor :border_i
|
||||
|
||||
attr_accessor :padding_left
|
||||
attr_accessor :padding_right
|
||||
|
||||
attr_accessor :width
|
||||
attr_accessor :alignment
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Numeric
|
||||
def bytes_to_human
|
||||
units = %w{B KB MB GB TB}
|
||||
e = (Math.log(self)/Math.log(1024)).floor
|
||||
s = '%.3f' % (to_f / 1024**e)
|
||||
e = (Math.log(abs)/Math.log(1024)).floor
|
||||
s = '%.3f' % (abs.to_f / 1024**e)
|
||||
s.sub(/\.?0*$/, ' ' + units[e])
|
||||
end
|
||||
end
|
||||
|
||||
# time calculations
|
||||
class Fixnum
|
||||
SECONDS_IN_DAY = 24 * 60 * 60
|
||||
|
||||
def days
|
||||
self * SECONDS_IN_DAY
|
||||
end
|
||||
|
||||
def ago
|
||||
Time.now - self
|
||||
end
|
||||
end
|
||||
|
||||
123
lib/common/models/vulnerability.rb
Executable file → Normal file
123
lib/common/models/vulnerability.rb
Executable file → Normal file
@@ -1,61 +1,62 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'vulnerability/output'
|
||||
require 'vulnerability/urls'
|
||||
|
||||
class Vulnerability
|
||||
include Vulnerability::Output
|
||||
include Vulnerability::Urls
|
||||
|
||||
attr_accessor :title, :references, :type, :fixed_in
|
||||
|
||||
#
|
||||
# @param [ String ] title The title of the vulnerability
|
||||
# @param [ String ] type The type of the vulnerability
|
||||
# @param [ Hash ] references References
|
||||
# @param [ String ] fixed_in Vuln fixed in Version X
|
||||
#
|
||||
# @return [ Vulnerability ]
|
||||
def initialize(title, type, references = {}, fixed_in = '')
|
||||
@title = title
|
||||
@type = type
|
||||
@references = references
|
||||
@fixed_in = fixed_in
|
||||
end
|
||||
|
||||
# @param [ Vulnerability ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
# :nocov:
|
||||
def ==(other)
|
||||
title == other.title &&
|
||||
type == other.type &&
|
||||
references == other.references &&
|
||||
fixed_in == other.fixed_in
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# Create the Vulnerability from the json_item
|
||||
#
|
||||
# @param [ Hash ] json_item
|
||||
#
|
||||
# @return [ Vulnerability ]
|
||||
def self.load_from_json_item(json_item)
|
||||
references = {}
|
||||
|
||||
%w(id url cve secunia osvdb metasploit exploitdb).each do |key|
|
||||
if json_item[key]
|
||||
json_item[key] = [json_item[key]] if json_item[key].class != Array
|
||||
references[key] = json_item[key]
|
||||
end
|
||||
end
|
||||
|
||||
new(
|
||||
json_item['title'],
|
||||
json_item['type'],
|
||||
references,
|
||||
json_item['fixed_in'],
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'vulnerability/output'
|
||||
require 'vulnerability/urls'
|
||||
|
||||
class Vulnerability
|
||||
include Vulnerability::Output
|
||||
include Vulnerability::Urls
|
||||
|
||||
attr_accessor :title, :references, :type, :fixed_in
|
||||
|
||||
#
|
||||
# @param [ String ] title The title of the vulnerability
|
||||
# @param [ String ] type The type of the vulnerability
|
||||
# @param [ Hash ] references References
|
||||
# @param [ String ] fixed_in Vuln fixed in Version X
|
||||
#
|
||||
# @return [ Vulnerability ]
|
||||
def initialize(title, type, references = {}, fixed_in = '')
|
||||
@title = title
|
||||
@type = type
|
||||
@references = references
|
||||
@fixed_in = fixed_in
|
||||
end
|
||||
|
||||
# @param [ Vulnerability ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
# :nocov:
|
||||
def ==(other)
|
||||
title == other.title &&
|
||||
type == other.type &&
|
||||
references == other.references &&
|
||||
fixed_in == other.fixed_in
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# Create the Vulnerability from the json_item
|
||||
#
|
||||
# @param [ Hash ] json_item
|
||||
#
|
||||
# @return [ Vulnerability ]
|
||||
def self.load_from_json_item(json_item)
|
||||
references = {}
|
||||
references['id'] = [json_item['id']]
|
||||
|
||||
%w(url cve secunia osvdb metasploit exploitdb).each do |key|
|
||||
if json_item['references'][key]
|
||||
json_item['references'][key] = [json_item['references'][key]] if json_item['references'][key].class != Array
|
||||
references[key] = json_item['references'][key]
|
||||
end
|
||||
end
|
||||
|
||||
new(
|
||||
json_item['title'],
|
||||
json_item['type'],
|
||||
references,
|
||||
json_item['fixed_in']
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
|
||||
class Vulnerability
|
||||
module Output
|
||||
|
||||
# output the vulnerability
|
||||
def output(verbose = false)
|
||||
puts
|
||||
puts critical("Title: #{title}")
|
||||
|
||||
references.each do |key, urls|
|
||||
methodname = "url_#{key}"
|
||||
|
||||
urls.each do |u|
|
||||
next unless respond_to?(methodname)
|
||||
url = send(methodname, u)
|
||||
puts " Reference: #{url}" if url
|
||||
end
|
||||
end
|
||||
unless fixed_in.nil?
|
||||
puts notice("Fixed in: #{fixed_in}")
|
||||
end
|
||||
|
||||
puts notice("Fixed in: #{fixed_in}") if fixed_in
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
224
lib/common/models/wp_item.rb
Executable file → Normal file
224
lib/common/models/wp_item.rb
Executable file → Normal file
@@ -1,103 +1,121 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_item/findable'
|
||||
require 'wp_item/versionable'
|
||||
require 'wp_item/vulnerable'
|
||||
require 'wp_item/existable'
|
||||
require 'wp_item/infos'
|
||||
require 'wp_item/output'
|
||||
|
||||
class WpItem
|
||||
|
||||
extend WpItem::Findable
|
||||
include WpItem::Versionable
|
||||
include WpItem::Vulnerable
|
||||
include WpItem::Existable
|
||||
include WpItem::Infos
|
||||
include WpItem::Output
|
||||
|
||||
attr_reader :path
|
||||
attr_accessor :name, :wp_content_dir, :wp_plugins_dir
|
||||
|
||||
# @return [ Array ]
|
||||
# Make it private ?
|
||||
def allowed_options
|
||||
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :vulns_file]
|
||||
end
|
||||
|
||||
# @param [ URI ] target_base_uri
|
||||
# @param [ Hash ] options See allowed_option
|
||||
#
|
||||
# @return [ WpItem ]
|
||||
def initialize(target_base_uri, options = {})
|
||||
|
||||
options[:wp_content_dir] ||= 'wp-content'
|
||||
options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins'
|
||||
|
||||
set_options(options)
|
||||
forge_uri(target_base_uri)
|
||||
end
|
||||
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ void ]
|
||||
def set_options(options)
|
||||
allowed_options.each do |allowed_option|
|
||||
if options.has_key?(allowed_option)
|
||||
method = :"#{allowed_option}="
|
||||
|
||||
if self.respond_to?(method)
|
||||
self.send(method, options[allowed_option])
|
||||
else
|
||||
raise "#{self.class} does not respond to #{method}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
private :set_options
|
||||
|
||||
# @param [ URI ] target_base_uri
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri
|
||||
end
|
||||
|
||||
# @return [ URI ] The uri to the WpItem, with the path if present
|
||||
def uri
|
||||
path ? @uri.merge(path) : @uri
|
||||
end
|
||||
|
||||
# @return [ String ] The url to the WpItem
|
||||
def url; uri.to_s end
|
||||
|
||||
# Sets the path
|
||||
#
|
||||
# Variable, such as $wp-plugins$ and $wp-content$ can be used
|
||||
# and will be replace by their value
|
||||
#
|
||||
# @param [ String ] path
|
||||
#
|
||||
# @return [ void ]
|
||||
def path=(path)
|
||||
@path = URI.encode(
|
||||
path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
|
||||
)
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def <=>(other)
|
||||
name <=> other.name
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def ==(other)
|
||||
name === other.name
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def ===(other)
|
||||
self == other && version === other.version
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_item/findable'
|
||||
require 'wp_item/versionable'
|
||||
require 'wp_item/vulnerable'
|
||||
require 'wp_item/existable'
|
||||
require 'wp_item/infos'
|
||||
require 'wp_item/output'
|
||||
|
||||
class WpItem
|
||||
|
||||
extend WpItem::Findable
|
||||
include WpItem::Versionable
|
||||
include WpItem::Vulnerable
|
||||
include WpItem::Existable
|
||||
include WpItem::Infos
|
||||
include WpItem::Output
|
||||
|
||||
attr_reader :path
|
||||
attr_accessor :name, :wp_content_dir, :wp_plugins_dir
|
||||
|
||||
# @return [ Array ]
|
||||
# Make it private ?
|
||||
def allowed_options
|
||||
[:name, :wp_content_dir, :wp_plugins_dir, :path, :version, :db_file]
|
||||
end
|
||||
|
||||
# @param [ URI ] target_base_uri
|
||||
# @param [ Hash ] options See allowed_option
|
||||
#
|
||||
# @return [ WpItem ]
|
||||
def initialize(target_base_uri, options = {})
|
||||
options[:wp_content_dir] ||= 'wp-content'
|
||||
options[:wp_plugins_dir] ||= options[:wp_content_dir] + '/plugins'
|
||||
|
||||
set_options(options)
|
||||
forge_uri(target_base_uri)
|
||||
end
|
||||
|
||||
def identifier
|
||||
@identifier ||= name
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def db_data
|
||||
@db_data ||= json(db_file)[identifier] || {}
|
||||
end
|
||||
|
||||
def latest_version
|
||||
db_data['latest_version']
|
||||
end
|
||||
|
||||
def last_updated
|
||||
db_data['last_updated']
|
||||
end
|
||||
|
||||
def popular?
|
||||
db_data['popular']
|
||||
end
|
||||
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ void ]
|
||||
def set_options(options)
|
||||
allowed_options.each do |allowed_option|
|
||||
if options.has_key?(allowed_option)
|
||||
method = :"#{allowed_option}="
|
||||
|
||||
if self.respond_to?(method)
|
||||
self.send(method, options[allowed_option])
|
||||
else
|
||||
raise "#{self.class} does not respond to #{method}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
private :set_options
|
||||
|
||||
# @param [ URI ] target_base_uri
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri
|
||||
end
|
||||
|
||||
# @return [ URI ] The uri to the WpItem, with the path if present
|
||||
def uri
|
||||
path ? @uri.merge(path) : @uri
|
||||
end
|
||||
|
||||
# @return [ String ] The url to the WpItem
|
||||
def url; uri.to_s end
|
||||
|
||||
# Sets the path
|
||||
#
|
||||
# Variable, such as $wp-plugins$ and $wp-content$ can be used
|
||||
# and will be replace by their value
|
||||
#
|
||||
# @param [ String ] path
|
||||
#
|
||||
# @return [ void ]
|
||||
def path=(path)
|
||||
@path = path.gsub(/\$wp-plugins\$/i, wp_plugins_dir).gsub(/\$wp-content\$/i, wp_content_dir)
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def <=>(other)
|
||||
name <=> other.name
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def ==(other)
|
||||
name === other.name
|
||||
end
|
||||
|
||||
# @param [ WpItem ] other
|
||||
def ===(other)
|
||||
self == other && version === other.version
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
100
lib/common/models/wp_item/existable.rb
Executable file → Normal file
100
lib/common/models/wp_item/existable.rb
Executable file → Normal file
@@ -1,50 +1,50 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
module Existable
|
||||
|
||||
# Check the existence of the WpItem
|
||||
# If the response is supplied, it's used for the verification
|
||||
# Otherwise a new request is done
|
||||
#
|
||||
# @param [ Hash ] options See exists_from_response?
|
||||
# @param [ Typhoeus::Response ] response
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists?(options = {}, response = nil)
|
||||
unless response
|
||||
response = Browser.get(url)
|
||||
end
|
||||
exists_from_response?(response, options)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ options ] options
|
||||
#
|
||||
# @option options [ Hash ] :error_404_hash The hash of the error 404 page
|
||||
# @option options [ Hash ] :homepage_hash The hash of the homepage
|
||||
# @option options [ Hash ] :exclude_content A regexp with the pattern to exclude from the body of the response
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists_from_response?(response, options = {})
|
||||
# 301 included as some items do a self-redirect
|
||||
# Redirects to the 404 and homepage should be ignored (unless dynamic content is used)
|
||||
# by the page hashes (error_404_hash & homepage_hash)
|
||||
if [200, 401, 403, 301].include?(response.code)
|
||||
if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash])
|
||||
if options[:exclude_content]
|
||||
unless response.body.match(options[:exclude_content])
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
module Existable
|
||||
|
||||
# Check the existence of the WpItem
|
||||
# If the response is supplied, it's used for the verification
|
||||
# Otherwise a new request is done
|
||||
#
|
||||
# @param [ Hash ] options See exists_from_response?
|
||||
# @param [ Typhoeus::Response ] response
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists?(options = {}, response = nil)
|
||||
unless response
|
||||
response = Browser.get(url)
|
||||
end
|
||||
exists_from_response?(response, options)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ options ] options
|
||||
#
|
||||
# @option options [ Hash ] :error_404_hash The hash of the error 404 page
|
||||
# @option options [ Hash ] :homepage_hash The hash of the homepage
|
||||
# @option options [ Hash ] :exclude_content A regexp with the pattern to exclude from the body of the response
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists_from_response?(response, options = {})
|
||||
# 301 included as some items do a self-redirect
|
||||
# Redirects to the 404 and homepage should be ignored (unless dynamic content is used)
|
||||
# by the page hashes (error_404_hash & homepage_hash)
|
||||
if [200, 401, 403, 301].include?(response.code)
|
||||
if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash])
|
||||
if options[:exclude_content]
|
||||
unless response.body.match(options[:exclude_content])
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
37
lib/common/models/wp_item/findable.rb
Executable file → Normal file
37
lib/common/models/wp_item/findable.rb
Executable file → Normal file
@@ -1,19 +1,18 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
attr_reader :found_from
|
||||
|
||||
# Sets the found_from attribute
|
||||
#
|
||||
# @param [ String ] method The method which found the WpItem
|
||||
#
|
||||
# @return [ void ]
|
||||
def found_from=(method)
|
||||
found = method[%r{find_from_(.*)}, 1]
|
||||
@found_from = found.gsub('_', ' ') if found
|
||||
end
|
||||
|
||||
module Findable
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
attr_reader :found_from
|
||||
|
||||
# Sets the found_from attribute
|
||||
#
|
||||
# @param [ String ] method The method which found the WpItem
|
||||
#
|
||||
# @return [ void ]
|
||||
def found_from=(method)
|
||||
@found_from = method.to_s.gsub(/find_from_/, '').gsub(/_/, ' ')
|
||||
end
|
||||
|
||||
module Findable
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,27 +5,6 @@ class WpItem
|
||||
# @uri is used instead of #uri to avoid the presence of the :path into it
|
||||
module Infos
|
||||
|
||||
# @return [ Boolean ]
|
||||
def has_readme?
|
||||
!readme_url.nil?
|
||||
end
|
||||
|
||||
# @return [ String,nil ] The url to the readme file, nil if not found
|
||||
def readme_url
|
||||
# See https://github.com/wpscanteam/wpscan/pull/737#issuecomment-66375445
|
||||
# for any question about the order
|
||||
%w{readme.txt README.txt Readme.txt ReadMe.txt README.TXT readme.TXT}.each do |readme|
|
||||
url = @uri.merge(readme).to_s
|
||||
return url if url_is_200?(url)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
def has_changelog?
|
||||
url_is_200?(changelog_url)
|
||||
end
|
||||
|
||||
# Checks if the url status code is 200
|
||||
#
|
||||
# @param [ String ] url
|
||||
@@ -35,9 +14,34 @@ class WpItem
|
||||
Browser.get(url).code == 200
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
def has_readme?
|
||||
!readme_url.nil?
|
||||
end
|
||||
|
||||
# @return [ String,nil ] The url to the readme file, nil if not found
|
||||
def readme_url
|
||||
# See https://github.com/wpscanteam/wpscan/pull/737#issuecomment-66375445
|
||||
# for any question about the order
|
||||
%w{readme.txt README.txt README.md readme.md Readme.txt}.each do |readme|
|
||||
url = @uri.merge(readme).to_s
|
||||
return url if url_is_200?(url)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
def has_changelog?
|
||||
!changelog_url.nil?
|
||||
end
|
||||
|
||||
# @return [ String ] The url to the changelog file
|
||||
def changelog_url
|
||||
@uri.merge('changelog.txt').to_s
|
||||
%w{changelog.txt CHANGELOG.md changelog.md}.each do |changelog|
|
||||
url = @uri.merge(changelog).to_s
|
||||
return url if url_is_200?(url)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
|
||||
@@ -5,12 +5,17 @@ class WpItem
|
||||
|
||||
# @return [ Void ]
|
||||
def output(verbose = false)
|
||||
outdated = VersionCompare.lesser?(version, latest_version) if latest_version
|
||||
|
||||
puts
|
||||
puts info("Name: #{self}") #this will also output the version number if detected
|
||||
puts " | Latest version: #{latest_version} #{'(up to date)' if version}" if latest_version && !outdated
|
||||
puts " | Last updated: #{last_updated}" if last_updated
|
||||
puts " | Location: #{url}"
|
||||
#puts " | WordPress: #{wordpress_url}" if wordpress_org_item?
|
||||
puts " | Readme: #{readme_url}" if has_readme?
|
||||
puts " | Changelog: #{changelog_url}" if has_changelog?
|
||||
puts warning("The version is out of date, the latest version is #{latest_version}") if latest_version && outdated
|
||||
|
||||
puts warning("Directory listing is enabled: #{url}") if has_directory_listing?
|
||||
puts warning("An error_log file has been found: #{error_log_url}") if has_error_log?
|
||||
|
||||
@@ -18,7 +23,7 @@ class WpItem
|
||||
|
||||
if version.nil? && vulnerabilities.length > 0
|
||||
puts
|
||||
puts warning('We could not determine a version so all vulnerabilities are printed out')
|
||||
puts warning('We could not determine the version installed. All of the past known vulnerabilities will be output to allow you to do your own manual investigation.')
|
||||
end
|
||||
|
||||
vulnerabilities.output
|
||||
|
||||
0
lib/common/models/wp_item/versionable.rb
Executable file → Normal file
0
lib/common/models/wp_item/versionable.rb
Executable file → Normal file
95
lib/common/models/wp_item/vulnerable.rb
Executable file → Normal file
95
lib/common/models/wp_item/vulnerable.rb
Executable file → Normal file
@@ -1,51 +1,44 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
module Vulnerable
|
||||
attr_accessor :vulns_file, :identifier
|
||||
|
||||
# Get the vulnerabilities associated to the WpItem
|
||||
# Filters out already fixed vulnerabilities
|
||||
#
|
||||
# @return [ Vulnerabilities ]
|
||||
def vulnerabilities
|
||||
json = json(vulns_file)
|
||||
vulnerabilities = Vulnerabilities.new
|
||||
|
||||
json.each do |item|
|
||||
asset = item[identifier]
|
||||
|
||||
next unless asset
|
||||
|
||||
asset['vulnerabilities'].each do |vulnerability|
|
||||
vulnerability = Vulnerability.load_from_json_item(vulnerability)
|
||||
vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
||||
end
|
||||
|
||||
break # No need to iterate any further
|
||||
end
|
||||
|
||||
vulnerabilities
|
||||
end
|
||||
|
||||
def vulnerable?
|
||||
vulnerabilities.empty? ? false : true
|
||||
end
|
||||
|
||||
# Checks if a item is vulnerable to a specific vulnerability
|
||||
#
|
||||
# @param [ Vulnerability ] vuln Vulnerability to check the item against
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def vulnerable_to?(vuln)
|
||||
if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
|
||||
unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version)
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpItem
|
||||
module Vulnerable
|
||||
attr_accessor :db_file, :identifier
|
||||
|
||||
# Get the vulnerabilities associated to the WpItem
|
||||
# Filters out already fixed vulnerabilities
|
||||
#
|
||||
# @return [ Vulnerabilities ]
|
||||
def vulnerabilities
|
||||
return @vulnerabilities if @vulnerabilities
|
||||
|
||||
@vulnerabilities = Vulnerabilities.new
|
||||
|
||||
[*db_data['vulnerabilities']].each do |vulnerability|
|
||||
vulnerability = Vulnerability.load_from_json_item(vulnerability)
|
||||
@vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
||||
end
|
||||
|
||||
@vulnerabilities
|
||||
end
|
||||
|
||||
def vulnerable?
|
||||
vulnerabilities.empty? ? false : true
|
||||
end
|
||||
|
||||
# Checks if a item is vulnerable to a specific vulnerability
|
||||
#
|
||||
# @param [ Vulnerability ] vuln Vulnerability to check the item against
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def vulnerable_to?(vuln)
|
||||
if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
|
||||
unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version)
|
||||
return true
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
33
lib/common/models/wp_plugin.rb
Executable file → Normal file
33
lib/common/models/wp_plugin.rb
Executable file → Normal file
@@ -1,17 +1,16 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_plugin/vulnerable'
|
||||
|
||||
class WpPlugin < WpItem
|
||||
include WpPlugin::Vulnerable
|
||||
|
||||
# Sets the @uri
|
||||
#
|
||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri.merge(URI.encode(wp_plugins_dir + '/' + name + '/'))
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpPlugin < WpItem
|
||||
# Sets the @uri
|
||||
#
|
||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri.merge("#{wp_plugins_dir}/#{url_encode(name)}/")
|
||||
end
|
||||
|
||||
def db_file
|
||||
@db_file ||= PLUGINS_FILE
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpPlugin < WpItem
|
||||
module Vulnerable
|
||||
|
||||
# @return [ String ] The path to the file containing vulnerabilities
|
||||
def vulns_file
|
||||
unless @vulns_file
|
||||
@vulns_file = PLUGINS_VULNS_FILE
|
||||
end
|
||||
@vulns_file
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def identifier
|
||||
@name
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
73
lib/common/models/wp_theme.rb
Executable file → Normal file
73
lib/common/models/wp_theme.rb
Executable file → Normal file
@@ -1,36 +1,37 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_theme/findable'
|
||||
require 'wp_theme/versionable'
|
||||
require 'wp_theme/vulnerable'
|
||||
require 'wp_theme/info'
|
||||
require 'wp_theme/output'
|
||||
require 'wp_theme/childtheme'
|
||||
|
||||
class WpTheme < WpItem
|
||||
extend WpTheme::Findable
|
||||
include WpTheme::Versionable
|
||||
include WpTheme::Vulnerable
|
||||
include WpTheme::Info
|
||||
include WpTheme::Output
|
||||
include WpTheme::Childtheme
|
||||
|
||||
attr_accessor :referenced_url
|
||||
|
||||
def allowed_options; super << :referenced_url end
|
||||
|
||||
# Sets the @uri
|
||||
#
|
||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri.merge(URI.encode(wp_content_dir + '/themes/' + name + '/'))
|
||||
end
|
||||
|
||||
# @return [ String ] The url to the theme stylesheet
|
||||
def style_url
|
||||
@uri.merge('style.css').to_s
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_theme/findable'
|
||||
require 'wp_theme/versionable'
|
||||
require 'wp_theme/info'
|
||||
require 'wp_theme/output'
|
||||
require 'wp_theme/childtheme'
|
||||
|
||||
class WpTheme < WpItem
|
||||
extend WpTheme::Findable
|
||||
include WpTheme::Versionable
|
||||
include WpTheme::Info
|
||||
include WpTheme::Output
|
||||
include WpTheme::Childtheme
|
||||
|
||||
attr_accessor :referenced_url
|
||||
|
||||
def allowed_options; super << :referenced_url end
|
||||
|
||||
# Sets the @uri
|
||||
#
|
||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||
#
|
||||
# @return [ void ]
|
||||
def forge_uri(target_base_uri)
|
||||
@uri = target_base_uri.merge("#{wp_content_dir}/themes/#{url_encode(name)}/")
|
||||
end
|
||||
|
||||
# @return [ String ] The url to the theme stylesheet
|
||||
def style_url
|
||||
@uri.merge('style.css').to_s
|
||||
end
|
||||
|
||||
def db_file
|
||||
@db_file ||= THEMES_FILE
|
||||
end
|
||||
end
|
||||
|
||||
128
lib/common/models/wp_theme/findable.rb
Executable file → Normal file
128
lib/common/models/wp_theme/findable.rb
Executable file → Normal file
@@ -1,64 +1,64 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Findable
|
||||
|
||||
# Find the main theme of the blog
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find(target_uri)
|
||||
methods.grep(/^find_from_/).each do |method|
|
||||
if wp_theme = self.send(method, target_uri)
|
||||
wp_theme.found_from = method
|
||||
|
||||
return wp_theme
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Discover the wordpress theme by parsing the css link rel
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find_from_css_link(target_uri)
|
||||
response = Browser.get_and_follow_location(target_uri.to_s)
|
||||
|
||||
# https + domain is optional because of relative links
|
||||
return unless response.body =~ %r{(?:https?://[^"']+/)?([^/\s]+)/themes/([^"'/]+)[^"']*/style.css}i
|
||||
|
||||
new(
|
||||
target_uri,
|
||||
name: Regexp.last_match[2],
|
||||
referenced_url: Regexp.last_match[0],
|
||||
wp_content_dir: Regexp.last_match[1]
|
||||
)
|
||||
end
|
||||
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find_from_wooframework(target_uri)
|
||||
body = Browser.get(target_uri.to_s).body
|
||||
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
|
||||
|
||||
if matches = regexp.match(body)
|
||||
woo_theme_name = matches[1]
|
||||
woo_theme_version = matches[2]
|
||||
#woo_framework_version = matches[3] # Not used at this time
|
||||
|
||||
return new(
|
||||
target_uri,
|
||||
name: woo_theme_name,
|
||||
version: woo_theme_version
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Findable
|
||||
|
||||
# Find the main theme of the blog
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find(target_uri)
|
||||
methods.grep(/^find_from_/).each do |method|
|
||||
if wp_theme = self.send(method, target_uri)
|
||||
wp_theme.found_from = method.to_s
|
||||
|
||||
return wp_theme
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Discover the wordpress theme by parsing the css link rel
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find_from_css_link(target_uri)
|
||||
response = Browser.get_and_follow_location(target_uri.to_s)
|
||||
|
||||
# https + domain is optional because of relative links
|
||||
return unless response.body =~ %r{(?:https?://[^"']+/)?([^"'/\s]+)/themes/([^"'/]+)[^"']*/style\.css}i
|
||||
|
||||
new(
|
||||
target_uri,
|
||||
name: Regexp.last_match[2],
|
||||
referenced_url: Regexp.last_match[0],
|
||||
wp_content_dir: Regexp.last_match[1]
|
||||
)
|
||||
end
|
||||
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
def find_from_wooframework(target_uri)
|
||||
body = Browser.get(target_uri.to_s).body
|
||||
regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />}
|
||||
|
||||
if matches = regexp.match(body)
|
||||
woo_theme_name = matches[1]
|
||||
woo_theme_version = matches[2]
|
||||
#woo_framework_version = matches[3] # Not used at this time
|
||||
|
||||
return new(
|
||||
target_uri,
|
||||
name: woo_theme_name,
|
||||
version: woo_theme_version
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,13 +6,13 @@ class WpTheme
|
||||
# @return [ Void ]
|
||||
def additional_output(verbose = false)
|
||||
parse_style
|
||||
|
||||
|
||||
theme_desc = verbose ? @theme_description : truncate(@theme_description, 100)
|
||||
puts " | Style URL: #{style_url}"
|
||||
puts " | Referenced style.css: #{referenced_url}" if referenced_url && referenced_url != style_url
|
||||
puts " | Theme Name: #{@theme_name}" if @theme_name
|
||||
puts " | Theme URI: #{@theme_uri}" if @theme_uri
|
||||
puts " | Description: #{theme_desc}"
|
||||
puts " | Description: #{theme_desc}" if theme_desc
|
||||
puts " | Author: #{@theme_author}" if @theme_author
|
||||
puts " | Author URI: #{@theme_author_uri}" if @theme_author_uri
|
||||
puts " | Template: #{@theme_template}" if @theme_template and verbose
|
||||
|
||||
18
lib/common/models/wp_theme/versionable.rb
Executable file → Normal file
18
lib/common/models/wp_theme/versionable.rb
Executable file → Normal file
@@ -1,9 +1,9 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Versionable
|
||||
def version
|
||||
@version ||= Browser.get(style_url).body[%r{Version:\s*(?!trunk)([0-9a-z\.-]+)}i, 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Versionable
|
||||
def version
|
||||
@version ||= Browser.get(style_url).body[%r{Version:\s*(?!trunk)([0-9a-z\.-]+)}i, 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Vulnerable
|
||||
|
||||
# @return [ String ] The path to the file containing vulnerabilities
|
||||
def vulns_file
|
||||
unless @vulns_file
|
||||
@vulns_file = THEMES_VULNS_FILE
|
||||
end
|
||||
@vulns_file
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def identifier
|
||||
@name
|
||||
end
|
||||
end
|
||||
end
|
||||
40
lib/common/models/wp_timthumb.rb
Executable file → Normal file
40
lib/common/models/wp_timthumb.rb
Executable file → Normal file
@@ -1,20 +1,20 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_timthumb/versionable'
|
||||
require 'wp_timthumb/existable'
|
||||
require 'wp_timthumb/output'
|
||||
require 'wp_timthumb/vulnerable'
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
include WpTimthumb::Versionable
|
||||
include WpTimthumb::Existable
|
||||
include WpTimthumb::Output
|
||||
include WpTimthumb::Vulnerable
|
||||
|
||||
# @param [ WpTimthumb ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ==(other)
|
||||
url == other.url
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_timthumb/versionable'
|
||||
require 'wp_timthumb/existable'
|
||||
require 'wp_timthumb/output'
|
||||
require 'wp_timthumb/vulnerable'
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
include WpTimthumb::Versionable
|
||||
include WpTimthumb::Existable
|
||||
include WpTimthumb::Output
|
||||
include WpTimthumb::Vulnerable
|
||||
|
||||
# @param [ WpTimthumb ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ==(other)
|
||||
url == other.url
|
||||
end
|
||||
end
|
||||
|
||||
48
lib/common/models/wp_timthumb/versionable.rb
Executable file → Normal file
48
lib/common/models/wp_timthumb/versionable.rb
Executable file → Normal file
@@ -1,24 +1,24 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
module Versionable
|
||||
|
||||
# Get the version from the body of an invalid request
|
||||
# See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426
|
||||
#
|
||||
# @return [ String ] The version
|
||||
def version
|
||||
unless @version
|
||||
response = Browser.get(url)
|
||||
@version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1]
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
"#{url}#{ ' v' + version if version}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
module Versionable
|
||||
|
||||
# Get the version from the body of an invalid request
|
||||
# See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#426
|
||||
#
|
||||
# @return [ String ] The version
|
||||
def version
|
||||
unless @version
|
||||
response = Browser.get(url)
|
||||
@version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1]
|
||||
end
|
||||
@version
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
"#{url}#{ ' v' + version if version}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
0
lib/common/models/wp_user.rb
Executable file → Normal file
0
lib/common/models/wp_user.rb
Executable file → Normal file
@@ -3,6 +3,8 @@
|
||||
class WpUser < WpItem
|
||||
module BruteForcable
|
||||
|
||||
attr_reader :progress_bar
|
||||
|
||||
# Brute force the user with the wordlist supplied
|
||||
#
|
||||
# It can take a long time to queue 2 million requests,
|
||||
@@ -25,9 +27,19 @@ class WpUser < WpItem
|
||||
hydra = browser.hydra
|
||||
queue_count = 0
|
||||
found = false
|
||||
progress_bar = self.progress_bar(count_file_lines(wordlist)+1, options)
|
||||
|
||||
File.open(wordlist).each do |password|
|
||||
if wordlist == '-'
|
||||
words = ARGF
|
||||
passwords_size = 10
|
||||
options[:starting_at] = 0
|
||||
else
|
||||
words = File.open(wordlist)
|
||||
passwords_size = count_file_lines(wordlist)+1
|
||||
end
|
||||
|
||||
create_progress_bar(passwords_size, options)
|
||||
|
||||
words.each do |password|
|
||||
password.chomp!
|
||||
|
||||
# A successfull login will redirect us to the redirect_to parameter
|
||||
@@ -40,9 +52,15 @@ class WpUser < WpItem
|
||||
request = login_request(password, redirect_url)
|
||||
|
||||
request.on_complete do |response|
|
||||
progress_bar.progress += 1 if options[:show_progression] && !found
|
||||
if options[:show_progression] && !found
|
||||
progress_bar.progress += 1
|
||||
percentage = progress_bar.progress.fdiv(progress_bar.total)
|
||||
if options[:starting_at] && percentage >= 0.8
|
||||
progress_bar.total *= 2
|
||||
end
|
||||
end
|
||||
|
||||
puts "\n Trying Username : #{login} Password : #{password}" if options[:verbose]
|
||||
progress_bar.log(" Trying Username: #{login} Password: #{password}") if options[:verbose]
|
||||
|
||||
if valid_password?(response, password, redirect_url, options)
|
||||
found = true
|
||||
@@ -57,7 +75,7 @@ class WpUser < WpItem
|
||||
if queue_count >= browser.max_threads
|
||||
hydra.run
|
||||
queue_count = 0
|
||||
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
|
||||
progress_bar.log(" Sent #{browser.max_threads} request/s ...") if options[:verbose]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -71,12 +89,13 @@ class WpUser < WpItem
|
||||
#
|
||||
# @return [ ProgressBar ]
|
||||
# :nocov:
|
||||
def progress_bar(passwords_size, options)
|
||||
def create_progress_bar(passwords_size, options)
|
||||
if options[:show_progression]
|
||||
ProgressBar.create(
|
||||
@progress_bar = ProgressBar.create(
|
||||
format: '%t %a <%B> (%c / %C) %P%% %e',
|
||||
title: " Brute Forcing '#{login}'",
|
||||
total: passwords_size
|
||||
total: passwords_size,
|
||||
starting_at: options[:starting_at]
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -107,20 +126,20 @@ class WpUser < WpItem
|
||||
progression = "#{info('[SUCCESS]')} Login : #{login} Password : #{password}\n\n"
|
||||
valid = true
|
||||
elsif response.body =~ /login_error/i
|
||||
verbose = "\n Incorrect login and/or password."
|
||||
verbose = "Incorrect login and/or password."
|
||||
elsif response.timed_out?
|
||||
progression = critical('ERROR: Request timed out.')
|
||||
elsif response.code == 0
|
||||
progression = critical("ERROR: No response from remote server. WAF/IPS? (#{response.return_message})")
|
||||
elsif response.code.to_s =~ /^50/
|
||||
progression = critical('ERROR: Server error, try reducing the number of threads.')
|
||||
progression = critical('ERROR: Server error, try reducing the number of threads or use the --throttle option.')
|
||||
else
|
||||
progression = critical("ERROR: We received an unknown response for #{password}...")
|
||||
verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
|
||||
progression = critical("ERROR: We received an unknown response for login: #{login} and password: #{password}")
|
||||
verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
|
||||
end
|
||||
|
||||
puts "\n " + progression if progression && options[:show_progression]
|
||||
puts verbose if verbose && options[:verbose]
|
||||
progress_bar.log(" #{progression}") if progression && options[:show_progression]
|
||||
progress_bar.log(" #{verbose}") if verbose && options[:verbose]
|
||||
|
||||
valid || false
|
||||
end
|
||||
|
||||
172
lib/common/models/wp_user/existable.rb
Executable file → Normal file
172
lib/common/models/wp_user/existable.rb
Executable file → Normal file
@@ -1,86 +1,86 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUser < WpItem
|
||||
module Existable
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists_from_response?(response, options = {})
|
||||
load_from_response(response)
|
||||
|
||||
@login ? true : false
|
||||
end
|
||||
|
||||
# Load the login and display_name from the response
|
||||
#
|
||||
# @param [ Typhoeus::Response ] response
|
||||
#
|
||||
# @return [ void ]
|
||||
def load_from_response(response)
|
||||
if response.code == 301 # login in location?
|
||||
location = response.headers_hash['Location']
|
||||
|
||||
return if location.nil? || location.empty?
|
||||
|
||||
@login = Existable.login_from_author_pattern(location)
|
||||
@display_name = Existable.display_name_from_body(
|
||||
Browser.get(location).body
|
||||
)
|
||||
elsif response.code == 200 # login in body?
|
||||
@login = Existable.login_from_body(response.body)
|
||||
@display_name = Existable.display_name_from_body(response.body)
|
||||
end
|
||||
end
|
||||
private :load_from_response
|
||||
|
||||
# @param [ String ] text
|
||||
#
|
||||
# @return [ String ] The login
|
||||
def self.login_from_author_pattern(text)
|
||||
return unless text =~ %r{/author/([^/\b]+)/?}i
|
||||
|
||||
Regexp.last_match[1].force_encoding('UTF-8')
|
||||
end
|
||||
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String ] The login
|
||||
def self.login_from_body(body)
|
||||
# Feed URL with Permalinks
|
||||
login = WpUser::Existable.login_from_author_pattern(body)
|
||||
|
||||
unless login
|
||||
# No Permalinks
|
||||
login = body[%r{<body class="archive author author-([^\s]+)[ "]}i, 1]
|
||||
login ? login.force_encoding('UTF-8') : nil
|
||||
end
|
||||
|
||||
login
|
||||
end
|
||||
|
||||
# @note Some bodies are encoded in ASCII-8BIT, and Nokogiri doesn't support it
|
||||
# So it's forced to UTF-8 when this encoding is detected
|
||||
#
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String ] The display_name
|
||||
def self.display_name_from_body(body)
|
||||
if title_tag = body[%r{<title>([^<]+)</title>}i, 1]
|
||||
title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT
|
||||
title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s
|
||||
# & are not decoded with Nokogiri
|
||||
title_tag.gsub!('&', '&')
|
||||
|
||||
# replace UTF chars like » with dummy character
|
||||
title_tag.gsub!(/&#(\d+);/, '|')
|
||||
|
||||
name = title_tag[%r{([^|«»]+) }, 1]
|
||||
|
||||
return name.strip if name
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpUser < WpItem
|
||||
module Existable
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists_from_response?(response, options = {})
|
||||
load_from_response(response)
|
||||
|
||||
@login ? true : false
|
||||
end
|
||||
|
||||
# Load the login and display_name from the response
|
||||
#
|
||||
# @param [ Typhoeus::Response ] response
|
||||
#
|
||||
# @return [ void ]
|
||||
def load_from_response(response)
|
||||
if response.code == 301 # login in location?
|
||||
location = response.headers_hash['Location']
|
||||
|
||||
return if location.nil? || location.empty?
|
||||
|
||||
@login = Existable.login_from_author_pattern(location)
|
||||
@display_name = Existable.display_name_from_body(
|
||||
Browser.get(location).body
|
||||
)
|
||||
elsif response.code == 200 # login in body?
|
||||
@login = Existable.login_from_body(response.body)
|
||||
@display_name = Existable.display_name_from_body(response.body)
|
||||
end
|
||||
end
|
||||
private :load_from_response
|
||||
|
||||
# @param [ String ] text
|
||||
#
|
||||
# @return [ String ] The login
|
||||
def self.login_from_author_pattern(text)
|
||||
return unless text =~ %r{/author/([^/\b"']+)/?}i
|
||||
|
||||
Regexp.last_match[1].force_encoding('UTF-8')
|
||||
end
|
||||
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String ] The login
|
||||
def self.login_from_body(body)
|
||||
# Feed URL with Permalinks
|
||||
login = WpUser::Existable.login_from_author_pattern(body)
|
||||
|
||||
unless login
|
||||
# No Permalinks
|
||||
login = body[%r{<body class="archive author author-([^\s]+)[ "]}i, 1]
|
||||
login ? login.force_encoding('UTF-8') : nil
|
||||
end
|
||||
|
||||
login
|
||||
end
|
||||
|
||||
# @note Some bodies are encoded in ASCII-8BIT, and Nokogiri doesn't support it
|
||||
# So it's forced to UTF-8 when this encoding is detected
|
||||
#
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String ] The display_name
|
||||
def self.display_name_from_body(body)
|
||||
if title_tag = body[%r{<title>([^<]+)</title>}i, 1]
|
||||
title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT
|
||||
title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s
|
||||
# & are not decoded with Nokogiri
|
||||
title_tag.gsub!('&', '&')
|
||||
|
||||
# replace UTF chars like » with dummy character
|
||||
title_tag.gsub!(/&#(\d+);/, '|')
|
||||
|
||||
name = title_tag[%r{([^|«»]+) }, 1]
|
||||
|
||||
return name.strip if name
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
84
lib/common/models/wp_version.rb
Executable file → Normal file
84
lib/common/models/wp_version.rb
Executable file → Normal file
@@ -1,33 +1,51 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_version/findable'
|
||||
require 'wp_version/vulnerable'
|
||||
require 'wp_version/output'
|
||||
|
||||
class WpVersion < WpItem
|
||||
|
||||
extend WpVersion::Findable
|
||||
include WpVersion::Vulnerable
|
||||
include WpVersion::Output
|
||||
|
||||
# The version number
|
||||
attr_accessor :number
|
||||
|
||||
# @return [ Array ]
|
||||
def allowed_options; super << :number << :found_from end
|
||||
|
||||
# @param [ WpVersion ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ==(other)
|
||||
number == other.number
|
||||
end
|
||||
|
||||
# @return [ Array<String> ] All the stable versions from version_file
|
||||
def self.all(versions_file = WP_VERSIONS_FILE)
|
||||
Nokogiri.XML(File.open(versions_file)).css('version').reduce([]) do |a, node|
|
||||
a << node.text.to_s
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'wp_version/findable'
|
||||
require 'wp_version/output'
|
||||
|
||||
class WpVersion < WpItem
|
||||
extend WpVersion::Findable
|
||||
include WpVersion::Output
|
||||
|
||||
# The version number
|
||||
attr_accessor :number, :metadata
|
||||
alias_method :version, :number # Needed to have the right behaviour in Vulnerable#vulnerable_to?
|
||||
|
||||
# @return [ Array ]
|
||||
def allowed_options; super << :number << :found_from end
|
||||
|
||||
def identifier
|
||||
@identifier ||= number
|
||||
end
|
||||
|
||||
def db_file
|
||||
@db_file ||= WORDPRESSES_FILE
|
||||
end
|
||||
|
||||
# @param [ WpVersion ] other
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def ==(other)
|
||||
number == other.number
|
||||
end
|
||||
|
||||
# @return [ Array<String> ] All the stable versions from version_file
|
||||
def self.all(versions_file = WP_VERSIONS_FILE)
|
||||
Nokogiri.XML(File.open(versions_file)).css('version').reduce([]) do |a, node|
|
||||
a << node.text.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# @return [ Hash ] Metadata for specific WP version from WORDPRESSES_FILE
|
||||
def metadata(version)
|
||||
json = json(db_file)
|
||||
|
||||
metadata = {}
|
||||
temp = json[version]
|
||||
if !temp.nil?
|
||||
metadata[:release_date] = temp['release_date']
|
||||
metadata[:changelog_url] = temp['changelog_url']
|
||||
end
|
||||
metadata
|
||||
end
|
||||
end
|
||||
|
||||
462
lib/common/models/wp_version/findable.rb
Executable file → Normal file
462
lib/common/models/wp_version/findable.rb
Executable file → Normal file
@@ -1,222 +1,240 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpVersion < WpItem
|
||||
|
||||
module Findable
|
||||
|
||||
# Find the version of the blog designated from target_uri
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ String ] wp_content_dir
|
||||
# @param [ String ] wp_plugins_dir
|
||||
#
|
||||
# @return [ WpVersion ]
|
||||
def find(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
methods.grep(/^find_from_/).each do |method|
|
||||
|
||||
if method === :find_from_advanced_fingerprinting
|
||||
version = send(method, target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
else
|
||||
version = send(method, target_uri)
|
||||
end
|
||||
|
||||
if version
|
||||
return new(target_uri, number: version, found_from: method)
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Used to check if the version is correct: must contain at least one dot.
|
||||
#
|
||||
# @return [ String ]
|
||||
def version_pattern
|
||||
'([^\r\n"\']+\.[^\r\n"\']+)'
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Returns the first match of <pattern> in the body of the url
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ Regex ] pattern
|
||||
# @param [ String ] path
|
||||
#
|
||||
# @return [ String ]
|
||||
def scan_url(target_uri, pattern, path = nil)
|
||||
url = path ? target_uri.merge(path).to_s : target_uri.to_s
|
||||
response = Browser.get_and_follow_location(url)
|
||||
|
||||
response.body[pattern, 1]
|
||||
end
|
||||
|
||||
#
|
||||
# DO NOT Change the order of the following methods
|
||||
# unless you know what you are doing
|
||||
# See WpVersion.find
|
||||
#
|
||||
|
||||
# Attempts to find the wordpress version from,
|
||||
# the generator meta tag in the html source.
|
||||
#
|
||||
# The meta tag can be removed however it seems,
|
||||
# that it is reinstated on upgrade.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_meta_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{name="generator" content="wordpress #{version_pattern}"}i
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS feed source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_rss_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<generator>http://wordpress.org/\?v=#{version_pattern}</generator>}i,
|
||||
'feed/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find WordPress version from,
|
||||
# the generator tag in the RDF feed source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_rdf_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{version_pattern}" />}i,
|
||||
'feed/rdf/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the Atom source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_atom_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<generator uri="http://wordpress.org/" version="#{version_pattern}">WordPress</generator>}i,
|
||||
'feed/atom/'
|
||||
)
|
||||
end
|
||||
|
||||
def find_from_stylesheets_numbers(target_uri)
|
||||
wp_versions = WpVersion.all
|
||||
found = {}
|
||||
pattern = /\bver=([0-9\.]+)/i
|
||||
|
||||
Nokogiri::HTML(Browser.get(target_uri.to_s).body).css('link,script').each do |tag|
|
||||
%w(href src).each do |attribute|
|
||||
attr_value = tag.attribute(attribute).to_s
|
||||
|
||||
next if attr_value.nil? || attr_value.empty?
|
||||
|
||||
uri = Addressable::URI.parse(attr_value)
|
||||
next unless uri.query && uri.query.match(pattern)
|
||||
|
||||
version = Regexp.last_match[1].to_s
|
||||
|
||||
found[version] ||= 0
|
||||
found[version] += 1
|
||||
end
|
||||
end
|
||||
|
||||
found.delete_if { |v, _| !wp_versions.include?(v) }
|
||||
|
||||
best_guess = found.sort_by(&:last).last
|
||||
# best_guess[0]: version number, [1] numbers of occurences
|
||||
best_guess && best_guess[1] > 1 ? best_guess[0] : nil
|
||||
end
|
||||
|
||||
# Uses data/wp_versions.xml to try to identify a
|
||||
# wordpress version.
|
||||
#
|
||||
# It does this by using client side file hashing
|
||||
#
|
||||
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ String ] wp_content_dir
|
||||
# @param [ String ] wp_plugins_dir
|
||||
# @param [ String ] versions_xml The path to the xml containing all versions
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
xml = xml(versions_xml)
|
||||
|
||||
# This wp_item will take care of encoding the path
|
||||
# and replace variables like $wp-content$ & $wp-plugins$
|
||||
wp_item = WpItem.new(target_uri,
|
||||
wp_content_dir: wp_content_dir,
|
||||
wp_plugins_dir: wp_plugins_dir)
|
||||
|
||||
xml.xpath('//file').each do |node|
|
||||
wp_item.path = node.attribute('src').text
|
||||
|
||||
response = Browser.get(wp_item.url)
|
||||
md5sum = Digest::MD5.hexdigest(response.body)
|
||||
|
||||
node.search('hash').each do |hash|
|
||||
if hash.attribute('md5').text == md5sum
|
||||
return hash.search('version').text
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the readme.html file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_readme(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<br />\sversion #{version_pattern}}i,
|
||||
'readme.html'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the sitemap.xml file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_sitemap_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{generator="wordpress/#{version_pattern}"}i,
|
||||
'sitemap.xml'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the p-links-opml.php file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_links_opml(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{generator="wordpress/#{version_pattern}"}i,
|
||||
'wp-links-opml.php'
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpVersion < WpItem
|
||||
|
||||
module Findable
|
||||
|
||||
# Find the version of the blog designated from target_uri
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ String ] wp_content_dir
|
||||
# @param [ String ] wp_plugins_dir
|
||||
#
|
||||
# @return [ WpVersion ]
|
||||
def find(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
versions = {}
|
||||
methods.grep(/^find_from_/).each do |method|
|
||||
|
||||
if method === :find_from_advanced_fingerprinting
|
||||
version = send(method, target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
else
|
||||
version = send(method, target_uri)
|
||||
end
|
||||
|
||||
if version
|
||||
if versions.key?(version)
|
||||
versions[version] << method.to_s
|
||||
else
|
||||
versions[version] = [ method.to_s ]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if versions.length > 0
|
||||
determined_version = versions.max_by { |k, v| v.length }
|
||||
if determined_version
|
||||
return new(target_uri, number: determined_version[0], found_from: determined_version[1].join(', '))
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Used to check if the version is correct: must contain at least one dot.
|
||||
#
|
||||
# @return [ String ]
|
||||
def version_pattern
|
||||
'([^\r\n"\',]+\.[^\r\n"\',]+)'
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Returns the first match of <pattern> in the body of the url
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ Regex ] pattern
|
||||
# @param [ String ] path
|
||||
#
|
||||
# @return [ String ]
|
||||
def scan_url(target_uri, pattern, path = nil)
|
||||
url = path ? target_uri.merge(path).to_s : target_uri.to_s
|
||||
response = Browser.get_and_follow_location(url)
|
||||
|
||||
response.body[pattern, 1]
|
||||
end
|
||||
|
||||
#
|
||||
# DO NOT Change the order of the following methods
|
||||
# unless you know what you are doing
|
||||
# See WpVersion.find
|
||||
#
|
||||
|
||||
# Attempts to find the wordpress version from,
|
||||
# the generator meta tag in the html source.
|
||||
#
|
||||
# The meta tag can be removed however it seems,
|
||||
# that it is reinstated on upgrade.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_meta_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{name="generator" content="wordpress #{version_pattern}.*"}i
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS feed source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_rss_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<generator>http://wordpress.org/\?v=#{version_pattern}</generator>}i,
|
||||
'feed/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find WordPress version from,
|
||||
# the generator tag in the RDF feed source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_rdf_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<admin:generatorAgent rdf:resource="http://wordpress.org/\?v=#{version_pattern}" />}i,
|
||||
'feed/rdf/'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the Atom source.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_atom_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{<generator uri="http://wordpress.org/" version="#{version_pattern}">WordPress</generator>}i,
|
||||
'feed/atom/'
|
||||
)
|
||||
end
|
||||
|
||||
# Uses data/wp_versions.xml to try to identify a
|
||||
# wordpress version.
|
||||
#
|
||||
# It does this by using client side file hashing
|
||||
#
|
||||
# /!\ Warning : this method might return false positive if the file used for fingerprinting is part of a theme (they can be updated)
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
# @param [ String ] wp_content_dir
|
||||
# @param [ String ] wp_plugins_dir
|
||||
# @param [ String ] versions_xml The path to the xml containing all versions
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_advanced_fingerprinting(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
xml = xml(versions_xml)
|
||||
|
||||
wp_item = WpItem.new(target_uri,
|
||||
wp_content_dir: wp_content_dir,
|
||||
wp_plugins_dir: wp_plugins_dir)
|
||||
|
||||
xml.xpath('//file').each do |node|
|
||||
wp_item.path = node.attribute('src').text
|
||||
|
||||
response = Browser.get(wp_item.url)
|
||||
md5sum = Digest::MD5.hexdigest(response.body)
|
||||
|
||||
node.search('hash').each do |hash|
|
||||
if hash.attribute('md5').text == md5sum
|
||||
return hash.search('version').text
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the readme.html file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_readme(target_uri)
|
||||
version = scan_url(
|
||||
target_uri,
|
||||
%r{<br />\sversion #{version_pattern}}i,
|
||||
'readme.html'
|
||||
)
|
||||
|
||||
# Since WP >= 4.7, the Readme only contains the major version
|
||||
VersionCompare.lesser?(version, '4.7') ? version : nil
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the sitemap.xml file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_sitemap_generator(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{generator="wordpress/#{version_pattern}"}i,
|
||||
'sitemap.xml'
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from the p-links-opml.php file.
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
def find_from_links_opml(target_uri)
|
||||
scan_url(
|
||||
target_uri,
|
||||
%r{generator="wordpress/#{version_pattern}"}i,
|
||||
'wp-links-opml.php'
|
||||
)
|
||||
end
|
||||
|
||||
def find_from_stylesheets_numbers(target_uri)
|
||||
wp_versions = WpVersion.all
|
||||
found = {}
|
||||
pattern = /\bver=([0-9\.]+)/i
|
||||
|
||||
Nokogiri::HTML(Browser.get(target_uri.to_s).body).css('link,script').each do |tag|
|
||||
%w(href src).each do |attribute|
|
||||
attr_value = tag.attribute(attribute).to_s
|
||||
|
||||
next if attr_value.nil? || attr_value.empty?
|
||||
|
||||
begin
|
||||
uri = Addressable::URI.parse(attr_value)
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
next
|
||||
end
|
||||
|
||||
next unless uri.query && uri.query.match(pattern)
|
||||
|
||||
version = Regexp.last_match[1].to_s
|
||||
|
||||
found[version] ||= 0
|
||||
found[version] += 1
|
||||
end
|
||||
end
|
||||
|
||||
found.delete_if { |v, _| !wp_versions.include?(v) }
|
||||
|
||||
best_guess = found.sort_by(&:last).last
|
||||
# best_guess[0]: version number, [1] numbers of occurences
|
||||
best_guess && best_guess[1] > 1 ? best_guess[0] : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,8 +4,16 @@ class WpVersion < WpItem
|
||||
module Output
|
||||
|
||||
def output(verbose = false)
|
||||
metadata = self.metadata(self.number)
|
||||
|
||||
puts
|
||||
puts info("WordPress version #{self.number} identified from #{self.found_from}")
|
||||
if verbose
|
||||
puts info("WordPress version #{self.number} identified from #{self.found_from}")
|
||||
puts " | Released: #{metadata[:release_date]}"
|
||||
puts " | Changelog: #{metadata[:changelog_url]}"
|
||||
else
|
||||
puts info("WordPress version #{self.number} #{"(Released on #{metadata[:release_date]}) identified from #{self.found_from}" if metadata[:release_date]}")
|
||||
end
|
||||
|
||||
vulnerabilities = self.vulnerabilities
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpVersion < WpItem
|
||||
module Vulnerable
|
||||
|
||||
# @return [ String ] The path to the file containing vulnerabilities
|
||||
def vulns_file
|
||||
unless @vulns_file
|
||||
@vulns_file = WP_VULNS_FILE
|
||||
end
|
||||
@vulns_file
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def identifier
|
||||
@number
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
# def vulns_xpath
|
||||
# "//wordpress[@version='#{@number}']/vulnerability"
|
||||
# end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -11,8 +11,8 @@ class VersionCompare
|
||||
# @return [ Boolean ]
|
||||
def self.lesser_or_equal?(version1, version2)
|
||||
# Prepend a '0' if the version starts with a '.'
|
||||
version1 = "0#{version1}" if version1 && version1[0,1] == '.'
|
||||
version2 = "0#{version2}" if version2 && version2[0,1] == '.'
|
||||
version1 = prepend_zero(version1)
|
||||
version2 = prepend_zero(version2)
|
||||
|
||||
return true if (version1 == version2)
|
||||
# Both versions must be set
|
||||
@@ -27,4 +27,36 @@ class VersionCompare
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
# Compares two version strings. Returns true if version1 < version2
|
||||
# and false otherwise
|
||||
#
|
||||
# @param [ String ] version1
|
||||
# @param [ String ] version2
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def self.lesser?(version1, version2)
|
||||
# Prepend a '0' if the version starts with a '.'
|
||||
version1 = prepend_zero(version1)
|
||||
version2 = prepend_zero(version2)
|
||||
|
||||
return false if (version1 == version2)
|
||||
# Both versions must be set
|
||||
return false unless (version1 and version2)
|
||||
return false if (version1.empty? or version2.empty?)
|
||||
begin
|
||||
return true if (Gem::Version.new(version1) < Gem::Version.new(version2))
|
||||
rescue ArgumentError => e
|
||||
# Example: ArgumentError: Malformed version number string a
|
||||
return false if e.message =~ /Malformed version number string/
|
||||
raise
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def self.prepend_zero(version)
|
||||
return nil if version.nil?
|
||||
version[0,1] == '.' ? "0#{version}" : version
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
require 'rubygems'
|
||||
|
||||
version = RUBY_VERSION.dup
|
||||
if Gem::Version.create(version) < Gem::Version.create(1.9)
|
||||
puts "Ruby >= 1.9 required to run wpscan (You have #{version})"
|
||||
|
||||
if Gem::Version.create(version) < Gem::Version.create(MIN_RUBY_VERSION)
|
||||
puts "Ruby >= #{MIN_RUBY_VERSION} required to run wpscan (You have #{version})"
|
||||
exit(1)
|
||||
end
|
||||
|
||||
@@ -13,25 +14,25 @@ Encoding.default_external = Encoding::UTF_8
|
||||
|
||||
begin
|
||||
# Standard libs
|
||||
require 'readline'
|
||||
require 'bundler/setup' unless kali_linux?
|
||||
require 'getoptlong'
|
||||
require 'optparse' # Will replace getoptlong
|
||||
require 'uri'
|
||||
require 'time'
|
||||
require 'resolv'
|
||||
require 'xmlrpc/client'
|
||||
require 'digest/md5'
|
||||
require 'digest/sha1'
|
||||
require 'readline'
|
||||
require 'base64'
|
||||
require 'rbconfig'
|
||||
require 'pp'
|
||||
require 'shellwords'
|
||||
require 'fileutils'
|
||||
require 'pathname'
|
||||
require 'cgi'
|
||||
# Third party libs
|
||||
require 'typhoeus'
|
||||
require 'json'
|
||||
require 'yajl/json_gem'
|
||||
require 'nokogiri'
|
||||
require 'terminal-table'
|
||||
require 'ruby-progressbar'
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'web_site/robots_txt'
|
||||
require 'web_site/humans_txt'
|
||||
require 'web_site/interesting_headers'
|
||||
require 'web_site/robots_txt'
|
||||
require 'web_site/security_txt'
|
||||
require 'web_site/sitemap'
|
||||
require 'web_site/sql_file_export'
|
||||
|
||||
class WebSite
|
||||
include WebSite::RobotsTxt
|
||||
include WebSite::HumansTxt
|
||||
include WebSite::InterestingHeaders
|
||||
include WebSite::RobotsTxt
|
||||
include WebSite::SecurityTxt
|
||||
include WebSite::Sitemap
|
||||
include WebSite::SqlFileExport
|
||||
|
||||
attr_reader :uri
|
||||
|
||||
@@ -21,6 +29,29 @@ class WebSite
|
||||
@uri.to_s
|
||||
end
|
||||
|
||||
# Checks if the remote website has ssl errors
|
||||
def ssl_error?
|
||||
return false unless @uri.scheme == 'https'
|
||||
c = get_root_path_return_code
|
||||
# http://www.rubydoc.info/github/typhoeus/ethon/Ethon/Easy:return_code
|
||||
return (
|
||||
c == :ssl_connect_error ||
|
||||
c == :peer_failed_verification ||
|
||||
c == :ssl_certproblem ||
|
||||
c == :ssl_cipher ||
|
||||
c == :ssl_cacert ||
|
||||
c == :ssl_cacert_badfile ||
|
||||
c == :ssl_issuer_error ||
|
||||
c == :ssl_crl_badfile ||
|
||||
c == :ssl_engine_setfailed ||
|
||||
c == :ssl_engine_notfound
|
||||
)
|
||||
end
|
||||
|
||||
def get_root_path_return_code
|
||||
Browser.get(@uri.to_s).return_code
|
||||
end
|
||||
|
||||
# Checks if the remote website is up.
|
||||
def online?
|
||||
Browser.get(@uri.to_s).code != 0
|
||||
@@ -68,15 +99,18 @@ class WebSite
|
||||
end
|
||||
|
||||
# Compute the MD5 of the page
|
||||
# Comments are deleted from the page to avoid cache generation details
|
||||
# Comments and scripts are deleted from the page to avoid cache generation details
|
||||
#
|
||||
# @param [ String, Typhoeus::Response ] page The url of the response of the page
|
||||
#
|
||||
# @return [ String ] The MD5 hash of the page
|
||||
def self.page_hash(page)
|
||||
page = Browser.get(page, { followlocation: true, cache_ttl: 0 }) unless page.is_a?(Typhoeus::Response)
|
||||
|
||||
Digest::MD5.hexdigest(page.body.gsub(/<!--.*?-->/m, ''))
|
||||
# remove comments
|
||||
page = page.body.gsub(/<!--.*?-->/m, '')
|
||||
# remove javascript stuff
|
||||
page = page.gsub(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/m, '')
|
||||
Digest::MD5.hexdigest(page)
|
||||
end
|
||||
|
||||
def homepage_hash
|
||||
@@ -95,13 +129,6 @@ class WebSite
|
||||
@error_404_hash
|
||||
end
|
||||
|
||||
# Will try to find the rss url in the homepage
|
||||
# Only the first one found is returned
|
||||
def rss_url
|
||||
homepage_body = Browser.get(@uri.to_s).body
|
||||
homepage_body[%r{<link .* type="application/rss\+xml" .* href="([^"]+)" />}, 1]
|
||||
end
|
||||
|
||||
# Only the first 700 bytes are checked to avoid the download
|
||||
# of the whole file which can be very huge (like 2 Go)
|
||||
#
|
||||
|
||||
13
lib/wpscan/web_site/humans_txt.rb
Normal file
13
lib/wpscan/web_site/humans_txt.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WebSite
|
||||
module HumansTxt
|
||||
|
||||
# Gets the humans.txt URL
|
||||
# @return [ String ]
|
||||
def humans_url
|
||||
@uri.clone.merge('humans.txt').to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -18,48 +18,53 @@ class WebSite
|
||||
# Parse robots.txt
|
||||
# @return [ Array ] URLs generated from robots.txt
|
||||
def parse_robots_txt
|
||||
return unless has_robots?
|
||||
|
||||
return_object = []
|
||||
|
||||
# Make request
|
||||
response = Browser.get(robots_url.to_s)
|
||||
body = response.body
|
||||
|
||||
# Get all allow and disallow urls
|
||||
entries = body.scan(/^(?:dis)?allow:\s*(.*)$/i)
|
||||
|
||||
# Did we get something?
|
||||
if entries
|
||||
entries.flatten!
|
||||
entries.compact.sort!
|
||||
# Remove any rubbish
|
||||
entries = clean_uri(entries)
|
||||
|
||||
# Sort
|
||||
entries.sort!
|
||||
|
||||
# Wordpress URL
|
||||
wordpress_path = @uri.path
|
||||
|
||||
# Each "boring" value as defined below, remove
|
||||
RobotsTxt.known_dirs.each do |d|
|
||||
entries.delete(d)
|
||||
# also delete when wordpress is installed in subdir
|
||||
# Also delete when wordpress is installed in subdir
|
||||
dir_with_subdir = "#{wordpress_path}/#{d}".gsub(/\/+/, '/')
|
||||
entries.delete(dir_with_subdir)
|
||||
end
|
||||
|
||||
entries.each do |d|
|
||||
begin
|
||||
temp = @uri.clone
|
||||
temp.path = d.strip
|
||||
rescue URI::Error
|
||||
temp = d.strip
|
||||
end
|
||||
return_object << temp.to_s
|
||||
end
|
||||
# Convert to full URIs
|
||||
return_object = full_uri(entries)
|
||||
end
|
||||
return_object
|
||||
return return_object
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Useful ~ "function do_robots()" -> https://github.com/WordPress/WordPress/blob/master/wp-includes/functions.php
|
||||
#
|
||||
# @return [ Array ]
|
||||
def self.known_dirs
|
||||
%w{
|
||||
/
|
||||
/wp-admin/
|
||||
/wp-admin/admin-ajax.php
|
||||
/wp-includes/
|
||||
/wp-content/
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
13
lib/wpscan/web_site/security_txt.rb
Normal file
13
lib/wpscan/web_site/security_txt.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WebSite
|
||||
module SecurityTxt
|
||||
|
||||
# Gets the security.txt URL
|
||||
# @return [ String ]
|
||||
def security_url
|
||||
@uri.clone.merge('.well-known/security.txt').to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
53
lib/wpscan/web_site/sitemap.rb
Normal file
53
lib/wpscan/web_site/sitemap.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WebSite
|
||||
module Sitemap
|
||||
|
||||
# Checks if a sitemap.txt file exists
|
||||
# @return [ Boolean ]
|
||||
def has_sitemap?
|
||||
# Make the request
|
||||
response = Browser.get(sitemap_url)
|
||||
|
||||
# Make sure its HTTP 200
|
||||
return false unless response.code == 200
|
||||
|
||||
# Is there a sitemap value?
|
||||
result = response.body.scan(/^sitemap\s*:\s*(.*)$/i)
|
||||
return true if result[0]
|
||||
return false
|
||||
end
|
||||
|
||||
# Get the robots.txt URL
|
||||
# @return [ String ]
|
||||
def sitemap_url
|
||||
@uri.clone.merge('robots.txt').to_s
|
||||
end
|
||||
|
||||
# Parse robots.txt
|
||||
# @return [ Array ] URLs generated from robots.txt
|
||||
def parse_sitemap
|
||||
return_object = []
|
||||
|
||||
# Make request
|
||||
response = Browser.get(sitemap_url.to_s)
|
||||
|
||||
# Get all allow and disallow urls
|
||||
entries = response.body.scan(/^sitemap\s*:\s*(.*)$/i)
|
||||
|
||||
# Did we get something?
|
||||
if entries
|
||||
# Remove any rubbish
|
||||
entries = clean_uri(entries)
|
||||
|
||||
# Sort
|
||||
entries.sort!
|
||||
|
||||
# Convert to full URIs
|
||||
return_object = full_uri(entries)
|
||||
end
|
||||
return return_object
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
35
lib/wpscan/web_site/sql_file_export.rb
Normal file
35
lib/wpscan/web_site/sql_file_export.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WebSite
|
||||
module SqlFileExport
|
||||
|
||||
# Checks if a .sql file exists
|
||||
# @return [ Array ]
|
||||
def sql_file_export
|
||||
export_files = []
|
||||
|
||||
self.sql_file_export_urls.each do |url|
|
||||
response = Browser.get(url)
|
||||
export_files << url if response.code == 200 && response.body =~ /INSERT INTO/
|
||||
end
|
||||
|
||||
export_files
|
||||
end
|
||||
|
||||
# Gets a .sql export file URL
|
||||
# @return [ Array ]
|
||||
def sql_file_export_urls
|
||||
urls = []
|
||||
host = @uri.host[/(^[\w|-]+)/,1]
|
||||
|
||||
files = ["#{host}.sql", "#{host}.sql.gz", "#{host}.zip", 'db.sql', 'site.sql', 'database.sql',
|
||||
'data.sql', 'backup.sql', 'dump.sql', 'db_backup.sql', 'dbdump.sql', 'wordpress.sql', 'mysql.sql']
|
||||
|
||||
files.each do |file|
|
||||
urls << @uri.clone.merge(file).to_s
|
||||
end
|
||||
|
||||
urls
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,22 +1,26 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'web_site'
|
||||
require 'wp_target/wp_readme'
|
||||
require 'wp_target/wp_registrable'
|
||||
require 'wp_target/wp_api'
|
||||
require 'wp_target/wp_config_backup'
|
||||
require 'wp_target/wp_must_use_plugins'
|
||||
require 'wp_target/wp_login_protection'
|
||||
require 'wp_target/wp_custom_directories'
|
||||
require 'wp_target/wp_full_path_disclosure'
|
||||
require 'wp_target/wp_login_protection'
|
||||
require 'wp_target/wp_must_use_plugins'
|
||||
require 'wp_target/wp_readme'
|
||||
require 'wp_target/wp_registrable'
|
||||
require 'wp_target/wp_rss'
|
||||
|
||||
class WpTarget < WebSite
|
||||
include WpTarget::WpReadme
|
||||
include WpTarget::WpRegistrable
|
||||
include WpTarget::WpAPI
|
||||
include WpTarget::WpConfigBackup
|
||||
include WpTarget::WpMustUsePlugins
|
||||
include WpTarget::WpLoginProtection
|
||||
include WpTarget::WpCustomDirectories
|
||||
include WpTarget::WpFullPathDisclosure
|
||||
include WpTarget::WpLoginProtection
|
||||
include WpTarget::WpMustUsePlugins
|
||||
include WpTarget::WpReadme
|
||||
include WpTarget::WpRegistrable
|
||||
include WpTarget::WpRSS
|
||||
|
||||
attr_reader :verbose
|
||||
|
||||
@@ -28,8 +32,13 @@ class WpTarget < WebSite
|
||||
@wp_content_dir = options[:wp_content_dir]
|
||||
@wp_plugins_dir = options[:wp_plugins_dir]
|
||||
@multisite = nil
|
||||
@vhost = options[:vhost]
|
||||
|
||||
Browser.instance.referer = url
|
||||
if @vhost
|
||||
Browser.instance.vhost = @vhost
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# check if the target website is
|
||||
@@ -44,11 +53,7 @@ class WpTarget < WebSite
|
||||
fail "The target is responding with a 403, this might be due to a WAF or a plugin.\n" \
|
||||
'You should try to supply a valid user-agent via the --user-agent option or use the --random-agent option' if response.code == 403
|
||||
|
||||
if wp_content_dir
|
||||
dir = wp_content_dir
|
||||
else
|
||||
dir = 'wp-content'
|
||||
end
|
||||
dir = wp_content_dir ? wp_content_dir : 'wp-content'
|
||||
|
||||
if response.body =~ /["'][^"']*\/#{Regexp.escape(dir)}\/[^"']*["']/i
|
||||
wordpress = true
|
||||
@@ -134,6 +139,11 @@ class WpTarget < WebSite
|
||||
@uri.merge("#{wp_content_dir}/uploads/").to_s
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def includes_dir_url
|
||||
@uri.merge("wp-includes/").to_s
|
||||
end
|
||||
|
||||
# Script for replacing strings in wordpress databases
|
||||
# reveals database credentials after hitting submit
|
||||
# http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
|
||||
@@ -149,7 +159,26 @@ class WpTarget < WebSite
|
||||
resp.code == 200 && resp.body[%r{by interconnect}i]
|
||||
end
|
||||
|
||||
# Script used to recover locked out admin users
|
||||
# http://yoast.com/emergency-wordpress-access/
|
||||
# https://codex.wordpress.org/User:MichaelH/Orphaned_Plugins_needing_Adoption/Emergency
|
||||
#
|
||||
# @return [ String ]
|
||||
def emergency_url
|
||||
@uri.merge('emergency.php').to_s
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
def emergency_exists?
|
||||
resp = Browser.get(emergency_url)
|
||||
resp.code == 200 && resp.body[%r{password}i]
|
||||
end
|
||||
|
||||
def upload_directory_listing_enabled?
|
||||
directory_listing_enabled?(upload_dir_url)
|
||||
end
|
||||
|
||||
def include_directory_listing_enabled?
|
||||
directory_listing_enabled?(includes_dir_url)
|
||||
end
|
||||
end
|
||||
|
||||
86
lib/wpscan/wp_target/wp_api.rb
Normal file
86
lib/wpscan/wp_target/wp_api.rb
Normal file
@@ -0,0 +1,86 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTarget < WebSite
|
||||
module WpAPI
|
||||
|
||||
# Checks to see if the REST API is enabled
|
||||
#
|
||||
# This by default in a WordPress installation since 4.5+
|
||||
# @return [ Boolean ]
|
||||
def has_api?(url)
|
||||
# Make the request
|
||||
response = Browser.get(url)
|
||||
|
||||
# Able to view the output?
|
||||
if valid_json?(response.body) && response.body != ''
|
||||
# Read in JSON
|
||||
data = JSON.parse(response.body)
|
||||
|
||||
# If there is nothing there, return false
|
||||
if data.empty?
|
||||
return false
|
||||
# WAF/API disabled response
|
||||
elsif data.include?('message') and data['message'] =~ /Only authenticated users can access the REST API/
|
||||
return false
|
||||
# Success!
|
||||
elsif response.code == 200
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
# Something went wrong
|
||||
return false
|
||||
end
|
||||
|
||||
# @return [ String ] The API/JSON URL
|
||||
def json_url
|
||||
@uri.merge('/wp-json/').to_s
|
||||
end
|
||||
|
||||
# @return [ String ] The API/JSON URL to show users
|
||||
def json_users_url
|
||||
@uri.merge('/wp-json/wp/v2/users').to_s
|
||||
end
|
||||
|
||||
# @return [ String ] The API/JSON URL to show users
|
||||
def json_get_users(url)
|
||||
# Variables
|
||||
users = []
|
||||
|
||||
# Make the request
|
||||
response = Browser.get(url)
|
||||
|
||||
# If not HTTP 200, return false
|
||||
return false unless response.code == 200
|
||||
|
||||
# Able to view the output?
|
||||
return false unless valid_json?(response.body)
|
||||
|
||||
# Read in JSON
|
||||
data = JSON.parse(response.body)
|
||||
|
||||
# If there is nothing there, return false
|
||||
return false if data.empty?
|
||||
|
||||
# Add to array
|
||||
data.each do |child|
|
||||
row = [ child['id'], child['name'], child['link'] ]
|
||||
users << row
|
||||
end
|
||||
|
||||
# Sort and uniq
|
||||
users = users.sort.uniq
|
||||
|
||||
if users and users.size >= 1
|
||||
# Feedback
|
||||
grammar = grammar_s(users.size)
|
||||
puts warning("#{users.size} user#{grammar} exposed via API: #{json_users_url}")
|
||||
|
||||
# Print results
|
||||
table = Terminal::Table.new(headings: ['ID', 'Name', 'URL'],
|
||||
rows: users)
|
||||
puts table
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -14,7 +14,7 @@ class WpTarget < WebSite
|
||||
queue_count = 0
|
||||
|
||||
backups.each do |file|
|
||||
file_url = @uri.merge(URI.escape(file)).to_s
|
||||
file_url = @uri.merge(url_encode(file)).to_s
|
||||
request = browser.forge_request(file_url)
|
||||
|
||||
request.on_complete do |response|
|
||||
@@ -40,7 +40,7 @@ class WpTarget < WebSite
|
||||
# @return [ Array ]
|
||||
def self.config_backup_files
|
||||
%w{
|
||||
wp-config.php~ #wp-config.php# wp-config.php.save .wp-config.php.swp wp-config.php.swp wp-config.php.swo
|
||||
wp-config.php~ #wp-config.php# wp-config.php.save .wp-config.php.swp wp-config.php.swp wp-config.php.swo
|
||||
wp-config.php_bak wp-config.bak wp-config.php.bak wp-config.save wp-config.old wp-config.php.old
|
||||
wp-config.php.orig wp-config.orig wp-config.php.original wp-config.original wp-config.txt
|
||||
} # thanks to Feross.org for these
|
||||
|
||||
@@ -2,24 +2,21 @@
|
||||
|
||||
class WpTarget < WebSite
|
||||
module WpFullPathDisclosure
|
||||
|
||||
# Check for Full Path Disclosure (FPD)
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def has_full_path_disclosure?
|
||||
response = Browser.get(full_path_disclosure_url)
|
||||
response.body[%r{Fatal error}i] ? true : false
|
||||
Browser.get(full_path_disclosure_url).body[%r/Fatal error/i] ? true : false
|
||||
end
|
||||
|
||||
def full_path_disclosure_data
|
||||
return nil unless has_full_path_disclosure?
|
||||
Browser.get(full_path_disclosure_url).body[%r{<b>([^<]+\.php)</b>}, 1]
|
||||
Browser.get(full_path_disclosure_url).body[/Fatal error:.+? in (.+?) on/i, 1]
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def full_path_disclosure_url
|
||||
@uri.merge('wp-includes/rss-functions.php').to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
class WpTarget < WebSite
|
||||
module WpMustUsePlugins
|
||||
|
||||
# Checks to see if the must use plugin folder exists
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def has_must_use_plugins?
|
||||
response = Browser.get(must_use_url)
|
||||
|
||||
if response && WpTarget.valid_response_codes.include?(response.code)
|
||||
if response && [200, 401, 403].include?(response.code)
|
||||
hash = WebSite.page_hash(response)
|
||||
return true if hash != error_404_hash && hash != homepage_hash
|
||||
end
|
||||
@@ -21,6 +20,5 @@ class WpTarget < WebSite
|
||||
def must_use_url
|
||||
@uri.merge("#{wp_content_dir}/mu-plugins/").to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
73
lib/wpscan/wp_target/wp_rss.rb
Normal file
73
lib/wpscan/wp_target/wp_rss.rb
Normal file
@@ -0,0 +1,73 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTarget < WebSite
|
||||
module WpRSS
|
||||
|
||||
# Checks to see if there is an rss feed
|
||||
# Will try to find the rss url in the homepage
|
||||
# Only the first one found is returned
|
||||
#
|
||||
# This file comes by default in a WordPress installation
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def rss_url
|
||||
homepage_body = Browser.get(@uri.to_s).body
|
||||
# Format: <link rel="alternate" type="application/rss+xml" title=".*" href=".*" />
|
||||
homepage_body[%r{<link\s*.*\s*type=['|"]application\/rss\+xml['|"]\s*.*\stitle=".*" href=['|"]([^"]+)['|"]\s*\/?>}i, 1]
|
||||
end
|
||||
|
||||
|
||||
# Gets all the authors from the RSS feed
|
||||
#
|
||||
# @return [ string ]
|
||||
def rss_authors(url)
|
||||
# Variables
|
||||
users = []
|
||||
|
||||
# Make the request
|
||||
response = Browser.get(url, followlocation: true)
|
||||
|
||||
# Valid repose to view? HTTP 200?
|
||||
return false unless response.code == 200
|
||||
|
||||
# Get output
|
||||
data = response.body
|
||||
|
||||
# If there is nothing there, return false
|
||||
return false if data.empty?
|
||||
|
||||
begin
|
||||
# Read in RSS/XML
|
||||
xml = Nokogiri::XML(data)
|
||||
rescue
|
||||
puts critical("Missformed XML")
|
||||
return false
|
||||
end
|
||||
|
||||
begin
|
||||
# Look for <dc:creator> item
|
||||
xml.xpath('//item/dc:creator').each do |node|
|
||||
#Format: <dc:creator><![CDATA[.*]]></dc:creator>
|
||||
users << [%r{.*}i.match(node).to_s]
|
||||
end
|
||||
rescue
|
||||
puts critical("Missing Author field. Maybe non-standard WordPress RSS feed?")
|
||||
return false
|
||||
end
|
||||
|
||||
# Sort and uniq
|
||||
users = users.sort_by { |user| user.to_s.downcase }.uniq
|
||||
|
||||
if users and users.size >= 1
|
||||
# Feedback
|
||||
grammar = grammar_s(users.size)
|
||||
puts warning("Detected #{users.size} user#{grammar} from RSS feed:")
|
||||
|
||||
# Print results
|
||||
table = Terminal::Table.new(headings: ['Name'],
|
||||
rows: users)
|
||||
puts table
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../common/common_helper')
|
||||
require File.expand_path(File.join(__dir__, '..', 'common', 'common_helper'))
|
||||
|
||||
require_files_from_directory(WPSCAN_LIB_DIR, '**/*.rb')
|
||||
|
||||
@@ -28,9 +28,12 @@ def usage
|
||||
puts '-Enumerate installed themes ...'
|
||||
puts "ruby #{script_name} --url www.example.com --enumerate t"
|
||||
puts
|
||||
puts '-Enumerate users ...'
|
||||
puts '-Enumerate users (from 1 - 10)...'
|
||||
puts "ruby #{script_name} --url www.example.com --enumerate u"
|
||||
puts
|
||||
puts '-Enumerate users (from 1 - 20)...'
|
||||
puts "ruby #{script_name} --url www.example.com --enumerate u[1-20]"
|
||||
puts
|
||||
puts '-Enumerate installed timthumbs ...'
|
||||
puts "ruby #{script_name} --url www.example.com --enumerate tt"
|
||||
puts
|
||||
@@ -46,7 +49,7 @@ def usage
|
||||
puts '-Use custom plugins directory ...'
|
||||
puts "ruby #{script_name} -u www.example.com --wp-plugins-dir wp-content/custom-plugins"
|
||||
puts
|
||||
puts '-Update the DB ...'
|
||||
puts '-Update the Database ...'
|
||||
puts "ruby #{script_name} --update"
|
||||
puts
|
||||
puts '-Debug output ...'
|
||||
@@ -62,7 +65,7 @@ def help
|
||||
puts
|
||||
puts 'Some values are settable in a config file, see the example.conf.json'
|
||||
puts
|
||||
puts '--update Update to the database to the latest version.'
|
||||
puts '--update Update the database to the latest version.'
|
||||
puts '--url | -u <target url> The WordPress URL/domain to scan.'
|
||||
puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
|
||||
puts '--enumerate | -e [option(s)] Enumeration.'
|
||||
@@ -84,12 +87,17 @@ def help
|
||||
puts ' You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).'
|
||||
puts '--config-file | -c <config file> Use the specified config file, see the example.conf.json.'
|
||||
puts '--user-agent | -a <User-Agent> Use the specified User-Agent.'
|
||||
puts '--cookie <String> String to read cookies from.'
|
||||
puts '--cookie <string> String to read cookies from.'
|
||||
puts '--random-agent | -r Use a random User-Agent.'
|
||||
puts '--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not'
|
||||
puts '--batch Never ask for user input, use the default behaviour.'
|
||||
puts '--no-color Do not use colors in the output.'
|
||||
puts '--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it.'
|
||||
puts '--log [filename] Creates a log.txt file with WPScan\'s output if no filename is supplied. Otherwise the filename is used for logging.'
|
||||
puts '--no-banner Prevents the WPScan banner from being displayed.'
|
||||
puts '--disable-accept-header Prevents WPScan sending the Accept HTTP header.'
|
||||
puts '--disable-referer Prevents setting the Referer header.'
|
||||
puts '--disable-tls-checks Disables SSL/TLS certificate verification.'
|
||||
puts '--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specify it.'
|
||||
puts ' Subdirectories are allowed.'
|
||||
puts '--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory.'
|
||||
puts ' If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed'
|
||||
@@ -100,17 +108,70 @@ def help
|
||||
puts '--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.'
|
||||
puts '--username | -U <username> Only brute force the supplied username.'
|
||||
puts '--usernames <path-to-file> Only brute force the usernames from the file.'
|
||||
puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests.'
|
||||
puts '--cache-dir <cache-directory> Set the cache directory.'
|
||||
puts '--cache-ttl <cache-ttl> Typhoeus cache TTL.'
|
||||
puts '--request-timeout <request-timeout> Request Timeout.'
|
||||
puts '--connect-timeout <connect-timeout> Connect Timeout.'
|
||||
puts '--max-threads <max-threads> Maximum Threads.'
|
||||
puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests.'
|
||||
puts '--throttle <milliseconds> Milliseconds to wait before doing another web request. If used, the --threads should be set to 1.'
|
||||
puts '--help | -h This help screen.'
|
||||
puts '--verbose | -v Verbose output.'
|
||||
puts '--version Output the current version and exit.'
|
||||
puts
|
||||
end
|
||||
|
||||
|
||||
def clean_uri(entries)
|
||||
# Extract elements
|
||||
entries.flatten!
|
||||
# Remove any leading/trailing spaces
|
||||
entries.collect{|x| x.strip || x }
|
||||
# End Of Line issues
|
||||
entries.collect{|x| x.chomp! || x }
|
||||
# Remove nil's
|
||||
entries.compact
|
||||
# Unique values only
|
||||
entries.uniq!
|
||||
|
||||
return entries
|
||||
end
|
||||
|
||||
# Return the full URL
|
||||
def full_uri(entries)
|
||||
return_object = []
|
||||
# Each value now, try and make it a full URL
|
||||
entries.each do |d|
|
||||
begin
|
||||
temp = @uri.clone
|
||||
temp.path = d.strip
|
||||
rescue URI::Error
|
||||
temp = d.strip
|
||||
end
|
||||
return_object << temp.to_s
|
||||
end
|
||||
|
||||
return return_object
|
||||
end
|
||||
|
||||
# Parse humans.txt
|
||||
# @return [ Array ] URLs generated from humans.txt
|
||||
def parse_txt(url)
|
||||
return_object = []
|
||||
response = Browser.get(url.to_s)
|
||||
body = response.body
|
||||
|
||||
# Get all non-comments
|
||||
entries = body.split(/\n/)
|
||||
|
||||
# Did we get something?
|
||||
if entries
|
||||
# Remove any rubbish
|
||||
entries = clean_uri(entries)
|
||||
end
|
||||
return return_object
|
||||
end
|
||||
|
||||
|
||||
# Hook to check if the target if down during the scan
|
||||
# And have the number of requests performed to display at the end of the scan
|
||||
# The target is considered down after 30 requests with status = 0
|
||||
@@ -118,8 +179,15 @@ down = 0
|
||||
@total_requests_done = 0
|
||||
|
||||
Typhoeus.on_complete do |response|
|
||||
next if response.cached?
|
||||
|
||||
down += 1 if response.code == 0
|
||||
@total_requests_done += 1
|
||||
|
||||
fail 'The target seems to be down' if down >= 30
|
||||
|
||||
next unless Browser.instance.throttle > 0
|
||||
|
||||
sleep(Browser.instance.throttle)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpscanOptions
|
||||
|
||||
ACCESSOR_OPTIONS = [
|
||||
:batch,
|
||||
:enumerate_plugins,
|
||||
@@ -19,6 +18,7 @@ class WpscanOptions
|
||||
:proxy_auth,
|
||||
:threads,
|
||||
:url,
|
||||
:vhost,
|
||||
:wordlist,
|
||||
:force,
|
||||
:update,
|
||||
@@ -42,7 +42,12 @@ class WpscanOptions
|
||||
:request_timeout,
|
||||
:connect_timeout,
|
||||
:max_threads,
|
||||
:no_banner
|
||||
:no_banner,
|
||||
:throttle,
|
||||
:disable_accept_header,
|
||||
:disable_referer,
|
||||
:cache_dir,
|
||||
:disable_tls_checks
|
||||
]
|
||||
|
||||
attr_accessor *ACCESSOR_OPTIONS
|
||||
@@ -61,12 +66,16 @@ class WpscanOptions
|
||||
@url = URI.parse(add_http_protocol(url)).to_s
|
||||
end
|
||||
|
||||
def vhost=(vhost)
|
||||
@vhost = vhost
|
||||
end
|
||||
|
||||
def threads=(threads)
|
||||
@threads = threads.is_a?(Integer) ? threads : threads.to_i
|
||||
end
|
||||
|
||||
def wordlist=(wordlist)
|
||||
if File.exists?(wordlist)
|
||||
if File.exists?(wordlist) || wordlist == '-'
|
||||
@wordlist = wordlist
|
||||
else
|
||||
raise "The file #{wordlist} does not exist"
|
||||
@@ -143,11 +152,6 @@ class WpscanOptions
|
||||
end
|
||||
end
|
||||
|
||||
def basic_auth=(basic_auth)
|
||||
raise 'Invalid basic authentication format, login:password expected' if basic_auth.index(':').nil?
|
||||
@basic_auth = "Basic #{Base64.encode64(basic_auth).chomp}"
|
||||
end
|
||||
|
||||
def debug_output=(debug_output)
|
||||
Typhoeus::Config.verbose = debug_output
|
||||
end
|
||||
@@ -203,7 +207,9 @@ class WpscanOptions
|
||||
|
||||
enumerate_options_from_string(cli_value)
|
||||
else
|
||||
raise "Unknow option : #{cli_option} with value #{cli_value}"
|
||||
text = "Unknown option : #{cli_option}"
|
||||
text << " with value #{cli_value}" if (cli_value && !cli_value.empty?)
|
||||
raise text
|
||||
end
|
||||
end
|
||||
|
||||
@@ -246,6 +252,7 @@ class WpscanOptions
|
||||
def self.get_opt_long
|
||||
GetoptLong.new(
|
||||
['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--vhost',GetoptLong::OPTIONAL_ARGUMENT],
|
||||
['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT],
|
||||
['--username', '-U', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--usernames', GetoptLong::REQUIRED_ARGUMENT],
|
||||
@@ -270,12 +277,16 @@ class WpscanOptions
|
||||
['--cache-ttl', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--request-timeout', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--connect-timeout', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--max-threads', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--batch', GetoptLong::NO_ARGUMENT],
|
||||
['--no-color', GetoptLong::NO_ARGUMENT],
|
||||
['--cookie', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--log', GetoptLong::NO_ARGUMENT],
|
||||
['--no-banner', GetoptLong::NO_ARGUMENT]
|
||||
['--log', GetoptLong::OPTIONAL_ARGUMENT],
|
||||
['--no-banner', GetoptLong::NO_ARGUMENT],
|
||||
['--throttle', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--disable-accept-header', GetoptLong::NO_ARGUMENT],
|
||||
['--disable-referer', GetoptLong::NO_ARGUMENT],
|
||||
['--cache-dir', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--disable-tls-checks', GetoptLong::NO_ARGUMENT],
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -124,11 +124,10 @@ describe Browser do
|
||||
describe '#merge_request_params' do
|
||||
let(:params) { {} }
|
||||
let(:cookie_jar) { CACHE_DIR + '/browser/cookie-jar' }
|
||||
let(:user_agent) { 'SomeUA' }
|
||||
let(:default_expectation) {
|
||||
{
|
||||
cache_ttl: 250,
|
||||
headers: { 'User-Agent' => 'SomeUA' },
|
||||
ssl_verifypeer: false, ssl_verifyhost: 0,
|
||||
cookiejar: cookie_jar, cookiefile: cookie_jar,
|
||||
timeout: 60, connecttimeout: 10,
|
||||
maxredirs: 3,
|
||||
@@ -137,16 +136,25 @@ describe Browser do
|
||||
}
|
||||
|
||||
after :each do
|
||||
browser.user_agent = 'SomeUA'
|
||||
browser.user_agent = user_agent
|
||||
browser.cache_ttl = 250
|
||||
|
||||
expect(browser.merge_request_params(params)).to eq @expected
|
||||
expect(Typhoeus::Config.user_agent).to eq user_agent
|
||||
end
|
||||
|
||||
it 'sets the User-Agent header field and cache_ttl' do
|
||||
@expected = default_expectation
|
||||
end
|
||||
|
||||
context 'when @user_agent' do
|
||||
let(:user_agent) { 'test' }
|
||||
|
||||
it 'sets the User-Agent' do
|
||||
@expected = default_expectation
|
||||
end
|
||||
end
|
||||
|
||||
context 'when @proxy' do
|
||||
let(:proxy) { '127.0.0.1:9050' }
|
||||
let(:proxy_expectation) { default_expectation.merge(proxy: proxy) }
|
||||
@@ -160,7 +168,7 @@ describe Browser do
|
||||
it 'sets the proxy_auth' do
|
||||
browser.proxy = proxy
|
||||
browser.proxy_auth = 'user:pass'
|
||||
@expected = proxy_expectation.merge(proxyauth: 'user:pass')
|
||||
@expected = proxy_expectation.merge(proxyuserpwd: 'user:pass')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -177,7 +185,7 @@ describe Browser do
|
||||
it 'appends the basic_auth' do
|
||||
browser.basic_auth = 'user:pass'
|
||||
@expected = default_expectation.merge(
|
||||
headers: default_expectation[:headers].merge('Authorization' => 'Basic ' + Base64.encode64('user:pass').chomp)
|
||||
headers: { 'Authorization' => 'Basic ' + Base64.encode64('user:pass').chomp }
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -206,6 +214,13 @@ describe Browser do
|
||||
@expected = default_expectation.merge(cookie: cookie)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when @disable_tls_checks' do
|
||||
it 'disables tls checks' do
|
||||
browser.disable_tls_checks = true
|
||||
@expected = default_expectation.merge(ssl_verifypeer: 0, ssl_verifyhost: 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#forge_request' do
|
||||
|
||||
@@ -18,7 +18,7 @@ describe WpItems do
|
||||
vulnerable_targets_items: [ WpItem.new(uri, name: 'mr-smith'),
|
||||
WpItem.new(uri, name: 'neo')],
|
||||
|
||||
passive_detection: (1..13).reduce(WpItems.new) { |o, i| o << WpItem.new(uri, name: "detect-me-#{i}") }
|
||||
passive_detection: (1..15).reduce(WpItems.new) { |o, i| o << WpItem.new(uri, name: "detect-me-#{i}") }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -100,6 +100,13 @@ describe 'WpPlugins::Detectable' do
|
||||
expected.add('all-in-one-seo-pack', version: '2.0.3.1')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when google-universal-analytics detected' do
|
||||
it 'returns google-universal-analytics' do
|
||||
@body = '<!-- Google Universal Analytics for WordPress v2.4.2 -->'
|
||||
expected.add('google-universal-analytics', version: '2.4.2')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ describe WpPlugins do
|
||||
let(:expected) do
|
||||
{
|
||||
request_params: { cache_ttl: 0, followlocation: true },
|
||||
vulns_file: PLUGINS_VULNS_FILE,
|
||||
vulns_file: PLUGINS_FILE,
|
||||
targets_items_from_file: [ WpPlugin.new(uri, name: 'plugin1'),
|
||||
WpPlugin.new(uri, name:'plugin-2'),
|
||||
WpPlugin.new(uri, name: 'mr-smith')],
|
||||
|
||||
@@ -13,7 +13,7 @@ describe WpThemes do
|
||||
let(:expected) do
|
||||
{
|
||||
request_params: { cache_ttl: 0, followlocation: true },
|
||||
vulns_file: THEMES_VULNS_FILE,
|
||||
vulns_file: THEMES_FILE,
|
||||
targets_items_from_file: [ WpTheme.new(uri, name: '3colours'),
|
||||
WpTheme.new(uri, name:'42k'),
|
||||
WpTheme.new(uri, name: 'a-ri')],
|
||||
|
||||
@@ -26,10 +26,10 @@ describe 'WpTimthumbs::Detectable' do
|
||||
|
||||
def expected_targets_from_theme(theme_name)
|
||||
expected = []
|
||||
%w{
|
||||
%w(
|
||||
timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php
|
||||
}.each do |file|
|
||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php thumb.php
|
||||
).each do |file|
|
||||
path = "$wp-content$/themes/#{theme_name}/#{file}"
|
||||
expected << WpTimthumb.new(uri, path: path)
|
||||
end
|
||||
@@ -46,7 +46,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
after do
|
||||
targets = subject.send(:targets_items_from_file, file, wp_target)
|
||||
|
||||
expect(targets.map { |t| t.url }).to eq @expected.map { |t| t.url }
|
||||
expect(targets.map(&:url)).to eq @expected.map(&:url)
|
||||
end
|
||||
|
||||
context 'when an empty file' do
|
||||
@@ -71,7 +71,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
theme = 'hello-world'
|
||||
targets = subject.send(:theme_timthumbs, theme, wp_target)
|
||||
|
||||
expect(targets.map { |t| t.url }).to eq expected_targets_from_theme(theme).map { |t| t.url }
|
||||
expect(targets.map(&:url)).to eq expected_targets_from_theme(theme).map(&:url)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -81,7 +81,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
after do
|
||||
targets = subject.send(:targets_items, wp_target, options)
|
||||
|
||||
expect(targets.map { |t| t.url }).to eq @expected.sort.map { |t| t.url }
|
||||
expect(targets.map(&:url)).to match_array(@expected.map(&:url))
|
||||
end
|
||||
|
||||
context 'when no :theme_name' do
|
||||
@@ -101,7 +101,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
end
|
||||
|
||||
context 'when :theme_name' do
|
||||
let(:theme) { 'theme-name'}
|
||||
let(:theme) { 'theme-name' }
|
||||
|
||||
context 'when no :file' do
|
||||
let(:options) { { theme_name: theme } }
|
||||
|
||||
@@ -11,11 +11,11 @@ describe WpItem do
|
||||
end
|
||||
it_behaves_like 'WpItem::Versionable'
|
||||
it_behaves_like 'WpItem::Vulnerable' do
|
||||
let(:vulns_file) { MODELS_FIXTURES + '/wp_item/vulnerable/items_vulns.json' }
|
||||
let(:db_file) { MODELS_FIXTURES + '/wp_item/vulnerable/items_vulns.json' }
|
||||
let(:identifier) { 'neo' }
|
||||
let(:expected_refs) { {
|
||||
'id' => [2993],
|
||||
'url' => ['Ref 1,Ref 2'],
|
||||
'url' => ['Ref 1', 'Ref 2'],
|
||||
'cve' => ['2011-001'],
|
||||
'secunia' => ['secunia'],
|
||||
'osvdb' => ['osvdb'],
|
||||
@@ -105,11 +105,6 @@ describe WpItem do
|
||||
@expected = 'plugins/readme.txt'
|
||||
end
|
||||
end
|
||||
|
||||
it 'also encodes chars' do
|
||||
@path = 'some dir with spaces'
|
||||
@expected = 'some%20dir%20with%20spaces'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#uri' do
|
||||
|
||||
@@ -5,11 +5,11 @@ require 'spec_helper'
|
||||
describe WpPlugin do
|
||||
it_behaves_like 'WpPlugin::Vulnerable'
|
||||
it_behaves_like 'WpItem::Vulnerable' do
|
||||
let(:options) { { name: 'white-rabbit' } }
|
||||
let(:vulns_file) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins_vulns.json' }
|
||||
let(:options) { { name: 'white-rabbit' } }
|
||||
let(:db_file) { MODELS_FIXTURES + '/wp_plugin/vulnerable/plugins.json' }
|
||||
let(:expected_refs) { {
|
||||
'id' => [2993],
|
||||
'url' => ['Ref 1,Ref 2'],
|
||||
'url' => ['Ref 1', 'Ref 2'],
|
||||
'cve' => ['2011-001'],
|
||||
'secunia' => ['secunia'],
|
||||
'osvdb' => ['osvdb'],
|
||||
|
||||
@@ -45,9 +45,18 @@ describe 'WpTheme::Findable' do
|
||||
|
||||
# FIXME: the style_url should be checked in WpTheme for absolute / relative
|
||||
context 'when relative url is used' do
|
||||
it 'returns the WpTheme' do
|
||||
@file = 'relative_urls.html'
|
||||
@expected = WpTheme.new(uri, name: 'theme_name')
|
||||
context 'when leading slash' do
|
||||
it 'returns the WpTheme' do
|
||||
@file = 'relative_urls.html'
|
||||
@expected = WpTheme.new(uri, name: 'theme_name')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no leading slash' do
|
||||
it 'returns the WpTheme' do
|
||||
@file = 'relative_urls_missing_slash.html'
|
||||
@expected = WpTheme.new(uri, name: 'theme_name')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ describe WpTheme do
|
||||
it_behaves_like 'WpTheme::Vulnerable'
|
||||
it_behaves_like 'WpItem::Vulnerable' do
|
||||
let(:options) { { name: 'the-oracle' } }
|
||||
let(:vulns_file) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' }
|
||||
let(:db_file) { MODELS_FIXTURES + '/wp_theme/vulnerable/themes_vulns.json' }
|
||||
let(:expected_refs) { {
|
||||
'id' => [2993],
|
||||
'url' => ['Ref 1,Ref 2'],
|
||||
'url' => ['Ref 1', 'Ref 2'],
|
||||
'cve' => ['2011-001'],
|
||||
'secunia' => ['secunia'],
|
||||
'osvdb' => ['osvdb'],
|
||||
|
||||
@@ -65,6 +65,11 @@ describe 'WpVersion::Findable' do
|
||||
@fixture = '/3.5_minified.html'
|
||||
@expected = '3.5'
|
||||
end
|
||||
|
||||
it 'returns 3.5.1' do
|
||||
@fixture = '/3.5.1_mobile.html'
|
||||
@expected = '3.5.1'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -129,6 +134,13 @@ describe 'WpVersion::Findable' do
|
||||
@fixture = '/3.3.2.html'
|
||||
@expected = '3.3.2'
|
||||
end
|
||||
|
||||
context 'when version >= 4.7' do
|
||||
it 'returns nil' do
|
||||
@fixture = '/4.7.2.html'
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '::find_from_links_opml' do
|
||||
@@ -154,6 +166,22 @@ describe 'WpVersion::Findable' do
|
||||
end
|
||||
end
|
||||
|
||||
describe '::find_from_stylesheets_numbers' do
|
||||
after do
|
||||
fixture = fixtures_dir + 'stylesheet_numbers' + @fixture
|
||||
stub_request_to_fixture(url: uri, fixture: fixture)
|
||||
|
||||
expect(WpVersion.send(:find_from_stylesheets_numbers, uri)).to eq @expected
|
||||
end
|
||||
|
||||
context 'invalid url' do
|
||||
it 'returns nil' do
|
||||
@fixture = '/invalid_url.html'
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '::find' do
|
||||
# Stub all WpVersion::find_from_* to return nil
|
||||
def stub_all_to_nil
|
||||
|
||||
@@ -4,20 +4,6 @@ require 'spec_helper'
|
||||
|
||||
describe WpVersion do
|
||||
it_behaves_like 'WpVersion::Vulnerable'
|
||||
it_behaves_like 'WpItem::Vulnerable' do
|
||||
let(:options) { { number: '3.2' } }
|
||||
let(:vulns_file) { MODELS_FIXTURES + '/wp_version/vulnerable/versions_vulns.json' }
|
||||
let(:expected_refs) { {
|
||||
'id' => [2993],
|
||||
'url' => ['Ref 1,Ref 2'],
|
||||
'cve' => ['2011-001'],
|
||||
'secunia' => ['secunia'],
|
||||
'osvdb' => ['osvdb'],
|
||||
'metasploit' => ['exploit/ex1'],
|
||||
'exploitdb' => ['exploitdb']
|
||||
} }
|
||||
let(:expected_vulns) { Vulnerabilities.new << Vulnerability.new('Here I Am', 'SQLI', expected_refs) }
|
||||
end
|
||||
|
||||
subject(:wp_version) { WpVersion.new(uri, options) }
|
||||
let(:uri) { URI.parse('http://example.com/') }
|
||||
|
||||
@@ -121,4 +121,127 @@ describe 'VersionCompare' do
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '::lesser?' do
|
||||
context 'version checked is newer' do
|
||||
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_truthy }
|
||||
|
||||
it 'returns true' do
|
||||
@version1 = '1.0'
|
||||
@version2 = '2.0'
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
@version1 = '1.0'
|
||||
@version2 = '1.1'
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
@version1 = '1.0a'
|
||||
@version2 = '1.0b'
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
@version1 = '1.0'
|
||||
@version2 = '5000000'
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
@version1 = '0'
|
||||
@version2 = '1'
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
@version1 = '0.4.2b'
|
||||
@version2 = '2.3.3'
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
@version1 = '.47'
|
||||
@version2 = '.50.3'
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
@version1 = '2.5.9'
|
||||
@version2 = '2.5.10'
|
||||
end
|
||||
end
|
||||
|
||||
context 'version checked is older' do
|
||||
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
|
||||
|
||||
it 'returns false' do
|
||||
@version1 = '1'
|
||||
@version2 = '0'
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
@version1 = '1.0'
|
||||
@version2 = '0.5'
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
@version1 = '500000'
|
||||
@version2 = '1'
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
@version1 = '1.6.3.7.3.4'
|
||||
@version2 = '1.2.4.567.679.8.e'
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
@version1 = '.47'
|
||||
@version2 = '.46.3'
|
||||
end
|
||||
end
|
||||
|
||||
context 'version checked is the same' do
|
||||
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
|
||||
|
||||
it 'returns true' do
|
||||
@version1 = '1'
|
||||
@version2 = '1'
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
@version1 = 'a'
|
||||
@version2 = 'a'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'version number causes Gem::Version new Exception' do
|
||||
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
|
||||
|
||||
it 'returns false' do
|
||||
@version1 = 'a'
|
||||
@version2 = 'b'
|
||||
end
|
||||
end
|
||||
|
||||
context 'one version number is not set' do
|
||||
after { expect(VersionCompare::lesser?(@version1, @version2)).to be_falsey }
|
||||
|
||||
it 'returns false (version2 nil)' do
|
||||
@version1 = '1'
|
||||
@version2 = nil
|
||||
end
|
||||
|
||||
it 'returns false (version1 nil)' do
|
||||
@version1 = nil
|
||||
@version2 = '1'
|
||||
end
|
||||
|
||||
it 'returns false (version2 empty)' do
|
||||
@version1 = '1'
|
||||
@version2 = ''
|
||||
end
|
||||
|
||||
it 'returns false (version1 empty)' do
|
||||
@version1 = ''
|
||||
@version2 = '1'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -176,6 +176,17 @@ describe 'WebSite' do
|
||||
@expected = "yolo\n\n\nworld!"
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are scripts' do
|
||||
let(:page) {
|
||||
body = "yolo\n\n<script type=\"text/javascript\">alert('Hi');</script>\nworld!"
|
||||
Typhoeus::Response.new(body: body)
|
||||
}
|
||||
|
||||
it 'removes them' do
|
||||
@expected = "yolo\n\n\nworld!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#homepage_hash' do
|
||||
@@ -196,18 +207,6 @@ describe 'WebSite' do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#rss_url' do
|
||||
it 'returns nil if the url is not found' do
|
||||
stub_request(:get, web_site.url).to_return(body: 'No RSS link in this body !')
|
||||
expect(web_site.rss_url).to be_nil
|
||||
end
|
||||
|
||||
it "returns 'http://lamp-wp/wordpress-3.5/?feed=rss2'" do
|
||||
stub_request_to_fixture(url: web_site.url, fixture: fixtures_dir + '/rss_url/wordpress-3.5.htm')
|
||||
expect(web_site.rss_url).to be === 'http://lamp-wp/wordpress-3.5/?feed=rss2'
|
||||
end
|
||||
end
|
||||
|
||||
describe '::has_log?' do
|
||||
let(:log_url) { web_site.uri.merge('log.txt').to_s }
|
||||
let(:pattern) { %r{PHP Fatal error} }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
require File.expand_path(File.join(__dir__, 'wpscan_helper'))
|
||||
|
||||
describe WpTarget do
|
||||
subject(:wp_target) { WpTarget.new(target_url, options) }
|
||||
@@ -192,4 +192,27 @@ describe WpTarget do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#emergency_url' do
|
||||
it 'returns the correct url' do
|
||||
expect(wp_target.emergency_url).to eq 'http://example.localhost/emergency.php'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#emergency_exists?' do
|
||||
it 'returns true' do
|
||||
stub_request(:any, wp_target.emergency_url).to_return(status: 200, body: 'enter your password here')
|
||||
expect(wp_target.emergency_exists?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
stub_request(:any, wp_target.emergency_url).to_return(status: 500)
|
||||
expect(wp_target.emergency_exists?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
stub_request(:any, wp_target.emergency_url).to_return(status: 500, body: 'enter your password here')
|
||||
expect(wp_target.emergency_exists?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
|
||||
require File.expand_path(File.join(__dir__, 'wpscan_helper'))
|
||||
|
||||
describe 'WpscanOptions' do
|
||||
|
||||
@@ -186,23 +186,6 @@ describe 'WpscanOptions' do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#basic_auth=' do
|
||||
context 'invalid format' do
|
||||
it 'should raise an error if the : is missing' do
|
||||
expect { @wpscan_options.basic_auth = 'helloworld' }.to raise_error(
|
||||
RuntimeError, 'Invalid basic authentication format, login:password expected'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'valid format' do
|
||||
it "should add the 'Basic' word and do the encode64. See RFC 2617" do
|
||||
@wpscan_options.basic_auth = 'Aladdin:open sesame'
|
||||
expect(@wpscan_options.basic_auth).to eq 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_options?' do
|
||||
it 'should return false' do
|
||||
expect(@wpscan_options.has_options?).to be_falsey
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
<script type='text/javascript' src='//wp-content/items/detect-me-5/main.js'></script>
|
||||
|
||||
<link rel='stylesheet' id='ai1ec_style-css' href='//example.com/wp-content/items/detect-me-15/test.css' type='text/css' media='all' />
|
||||
|
||||
<style type="text/css">
|
||||
#fancybox-loading.fancybox-ie div {
|
||||
background: transparent;
|
||||
@@ -32,6 +34,7 @@
|
||||
@import url('/wp-content/items/detect-me-8/css/datatables.css?ver=1.9.4');
|
||||
@import url(/wp-content/items/detect-me-9/css/datatables.css?ver=1.9.4);
|
||||
@import url(//wp-content/items/detect-me-10/css/datatables.css?ver=1.9.4);
|
||||
@import url(//example.com/wp-content/items/detect-me-14/css/datatables.css?ver=1.9.4);
|
||||
/* ]]> */
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,58 +1,64 @@
|
||||
[
|
||||
{
|
||||
"mr-smith":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
{
|
||||
"mr-smith": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||
"references": {
|
||||
"url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"title":"Potential Authentication Cookie Forgery",
|
||||
"references":"https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"title":"Potential Authentication Cookie Forgery",
|
||||
"references": {
|
||||
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
|
||||
"osvdb":"105620",
|
||||
"cve":"2014-0166",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"cve":"2014-0166"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references": {
|
||||
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"osvdb":"105630",
|
||||
"cve":"2014-0165",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"cve":"2014-0165"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"references": {
|
||||
"osvdb":"105622",
|
||||
"secunia":"57769",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"secunia":"57769"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"neo":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references":"http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
"neo": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references": {
|
||||
"url": "http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,58 +1,64 @@
|
||||
[
|
||||
{
|
||||
"mr-smith":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
{
|
||||
"mr-smith": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||
"references": {
|
||||
"url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"title":"Potential Authentication Cookie Forgery",
|
||||
"references":"https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
|
||||
"osvdb":"105620",
|
||||
"cve":"2014-0166",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"title":"Potential Authentication Cookie Forgery",
|
||||
"references": {
|
||||
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"osvdb":"105620",
|
||||
"cve":"2014-0166",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references": {
|
||||
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"osvdb":"105630",
|
||||
"cve":"2014-0165",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"cve":"2014-0165"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"references": {
|
||||
"osvdb":"105622",
|
||||
"secunia":"57769",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"secunia":"57769"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"neo":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references":"http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
"neo": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references": {
|
||||
"url": "http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,58 +1,65 @@
|
||||
[
|
||||
{
|
||||
"shopperpress":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||
"references":"https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
{
|
||||
"shopperpress": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2989,
|
||||
"title":"Administrator-exploitable blind SQLi in WordPress 1.0 - 3.8.1",
|
||||
"references": {
|
||||
"url": "https://security.dxw.com/advisories/sqli-in-wordpress-3-6-1/,http://www.example.com"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"title":"Potential Authentication Cookie Forgery",
|
||||
"references":"https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
|
||||
"osvdb":"105620",
|
||||
"cve":"2014-0166",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:43:41.000Z"
|
||||
},
|
||||
{
|
||||
"id":2990,
|
||||
"title":"Potential Authentication Cookie Forgery",
|
||||
"references": {
|
||||
"url": "https://labs.mwrinfosecurity.com/blog/2014/04/11/wordpress-auth-cookie-forgery/,https://github.com/WordPress/WordPress/commit/78a915e0e5927cf413aa6c2cef2fca3dc587f8be",
|
||||
"osvdb":"105620",
|
||||
"cve":"2014-0166"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references":"https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"osvdb":"105630",
|
||||
"cve":"2014-0165",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2991,
|
||||
"title":"Privilege escalation: contributors publishing posts",
|
||||
"references": {
|
||||
"url": "https://github.com/wpscanteam/wpscan/wiki/CVE-2014-0165",
|
||||
"osvdb":"105630",
|
||||
"cve":"2014-0165"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
},
|
||||
{
|
||||
"id":2992,
|
||||
"title":"Plupload Unspecified XSS",
|
||||
"references": {
|
||||
"osvdb":"105622",
|
||||
"secunia":"57769",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"secunia":"57769"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z",
|
||||
"fixed_in":"3.8.2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"webfolio":{
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references":"http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101",
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
"webfolio": {
|
||||
"vulnerabilities":[
|
||||
{
|
||||
"id":2993,
|
||||
"title":"wp-admin/options-writing.php Cleartext Admin Credentials Disclosure",
|
||||
"references": {
|
||||
"url": "http://seclists.org/fulldisclosure/2013/Dec/135",
|
||||
"osvdb":"101101"
|
||||
},
|
||||
"created_at":"2014-07-28T12:10:07.000Z",
|
||||
"updated_at":"2014-07-28T12:10:07.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"id": "3911",
|
||||
"title": "Vuln Title",
|
||||
"url": "Ref 1,Ref 2",
|
||||
"secunia": "secunia",
|
||||
"osvdb": "osvdb",
|
||||
"cve": "2011-001",
|
||||
"metasploit": "exploit/ex1",
|
||||
"exploitdb": "exploitdb",
|
||||
"references":{
|
||||
"url": "Ref 1,Ref 2",
|
||||
"secunia": "secunia",
|
||||
"osvdb": "osvdb",
|
||||
"cve": "2011-001",
|
||||
"metasploit": "exploit/ex1",
|
||||
"exploitdb": "exploitdb"
|
||||
},
|
||||
"created_at": "2014-07-28T12:10:45.000Z",
|
||||
"updated_at": "2014-07-28T12:10:45.000Z",
|
||||
"type": "CSRF",
|
||||
|
||||
0
spec/samples/common/models/wp_item/error_log
Executable file → Normal file
0
spec/samples/common/models/wp_item/error_log
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user