wpscan 3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile.lock +139 -0
- data/LICENSE +74 -0
- data/README.md +146 -0
- data/app/app.rb +3 -0
- data/app/controllers.rb +6 -0
- data/app/controllers/brute_force.rb +126 -0
- data/app/controllers/core.rb +104 -0
- data/app/controllers/custom_directories.rb +23 -0
- data/app/controllers/enumeration.rb +53 -0
- data/app/controllers/enumeration/cli_options.rb +126 -0
- data/app/controllers/enumeration/enum_methods.rb +157 -0
- data/app/controllers/main_theme.rb +27 -0
- data/app/controllers/wp_version.rb +30 -0
- data/app/finders.rb +13 -0
- data/app/finders/config_backups.rb +17 -0
- data/app/finders/config_backups/known_filenames.rb +46 -0
- data/app/finders/interesting_findings.rb +33 -0
- data/app/finders/interesting_findings/backup_db.rb +25 -0
- data/app/finders/interesting_findings/debug_log.rb +20 -0
- data/app/finders/interesting_findings/duplicator_installer_log.rb +23 -0
- data/app/finders/interesting_findings/full_path_disclosure.rb +23 -0
- data/app/finders/interesting_findings/mu_plugins.rb +48 -0
- data/app/finders/interesting_findings/multisite.rb +29 -0
- data/app/finders/interesting_findings/readme.rb +26 -0
- data/app/finders/interesting_findings/registration.rb +31 -0
- data/app/finders/interesting_findings/tmm_db_migrate.rb +24 -0
- data/app/finders/interesting_findings/upload_directory_listing.rb +24 -0
- data/app/finders/interesting_findings/upload_sql_dump.rb +28 -0
- data/app/finders/main_theme.rb +22 -0
- data/app/finders/main_theme/css_style.rb +43 -0
- data/app/finders/main_theme/urls_in_homepage.rb +25 -0
- data/app/finders/main_theme/woo_framework_meta_generator.rb +22 -0
- data/app/finders/medias.rb +17 -0
- data/app/finders/medias/attachment_brute_forcing.rb +44 -0
- data/app/finders/plugin_version.rb +44 -0
- data/app/finders/plugin_version/layer_slider/translation_file.rb +40 -0
- data/app/finders/plugin_version/readme.rb +79 -0
- data/app/finders/plugin_version/revslider/release_log.rb +35 -0
- data/app/finders/plugin_version/sitepress_multilingual_cms/meta_generator.rb +27 -0
- data/app/finders/plugin_version/sitepress_multilingual_cms/version_parameter.rb +31 -0
- data/app/finders/plugin_version/w3_total_cache/headers.rb +28 -0
- data/app/finders/plugins.rb +24 -0
- data/app/finders/plugins/comments.rb +31 -0
- data/app/finders/plugins/headers.rb +36 -0
- data/app/finders/plugins/known_locations.rb +48 -0
- data/app/finders/plugins/urls_in_homepage.rb +29 -0
- data/app/finders/theme_version.rb +41 -0
- data/app/finders/theme_version/style.rb +43 -0
- data/app/finders/theme_version/woo_framework_meta_generator.rb +19 -0
- data/app/finders/themes.rb +20 -0
- data/app/finders/themes/known_locations.rb +48 -0
- data/app/finders/themes/urls_in_homepage.rb +23 -0
- data/app/finders/timthumb_version.rb +17 -0
- data/app/finders/timthumb_version/bad_request.rb +21 -0
- data/app/finders/timthumbs.rb +17 -0
- data/app/finders/timthumbs/known_locations.rb +56 -0
- data/app/finders/users.rb +24 -0
- data/app/finders/users/author_id_brute_forcing.rb +111 -0
- data/app/finders/users/author_posts.rb +61 -0
- data/app/finders/users/login_error_messages.rb +50 -0
- data/app/finders/users/wp_json_api.rb +31 -0
- data/app/finders/wp_items.rb +1 -0
- data/app/finders/wp_items/urls_in_homepage.rb +68 -0
- data/app/finders/wp_version.rb +34 -0
- data/app/finders/wp_version/atom_generator.rb +40 -0
- data/app/finders/wp_version/meta_generator.rb +27 -0
- data/app/finders/wp_version/opml_generator.rb +23 -0
- data/app/finders/wp_version/rdf_generator.rb +38 -0
- data/app/finders/wp_version/readme.rb +28 -0
- data/app/finders/wp_version/rss_generator.rb +43 -0
- data/app/finders/wp_version/sitemap_generator.rb +23 -0
- data/app/finders/wp_version/stylesheets.rb +55 -0
- data/app/finders/wp_version/unique_fingerprinting.rb +64 -0
- data/app/models.rb +10 -0
- data/app/models/config_backup.rb +5 -0
- data/app/models/interesting_finding.rb +6 -0
- data/app/models/media.rb +5 -0
- data/app/models/plugin.rb +25 -0
- data/app/models/theme.rb +99 -0
- data/app/models/timthumb.rb +74 -0
- data/app/models/user.rb +31 -0
- data/app/models/wp_item.rb +142 -0
- data/app/models/wp_version.rb +49 -0
- data/app/models/xml_rpc.rb +19 -0
- data/app/views/cli/brute_force/error.erb +1 -0
- data/app/views/cli/brute_force/found.erb +2 -0
- data/app/views/cli/brute_force/users.erb +9 -0
- data/app/views/cli/core/banner.erb +14 -0
- data/app/views/cli/core/db_update_finished.erb +8 -0
- data/app/views/cli/core/db_update_started.erb +1 -0
- data/app/views/cli/core/not_fully_configured.erb +1 -0
- data/app/views/cli/enumeration/config_backups.erb +11 -0
- data/app/views/cli/enumeration/medias.erb +11 -0
- data/app/views/cli/enumeration/plugins.erb +35 -0
- data/app/views/cli/enumeration/themes.erb +11 -0
- data/app/views/cli/enumeration/timthumbs.erb +18 -0
- data/app/views/cli/enumeration/users.erb +11 -0
- data/app/views/cli/finding.erb +32 -0
- data/app/views/cli/info.erb +1 -0
- data/app/views/cli/main_theme/theme.erb +6 -0
- data/app/views/cli/notice.erb +1 -0
- data/app/views/cli/theme.erb +64 -0
- data/app/views/cli/usage.erb +3 -0
- data/app/views/cli/vulnerability.erb +14 -0
- data/app/views/cli/wp_version/version.erb +6 -0
- data/app/views/json/brute_force/users.erb +10 -0
- data/app/views/json/core/banner.erb +12 -0
- data/app/views/json/core/db_update_finished.erb +2 -0
- data/app/views/json/core/db_update_started.erb +1 -0
- data/app/views/json/core/not_fully_configured.erb +1 -0
- data/app/views/json/enumeration/config_backups.erb +10 -0
- data/app/views/json/enumeration/medias.erb +10 -0
- data/app/views/json/enumeration/plugins.erb +25 -0
- data/app/views/json/enumeration/themes.erb +10 -0
- data/app/views/json/enumeration/timthumbs.erb +19 -0
- data/app/views/json/enumeration/users.erb +11 -0
- data/app/views/json/finding.erb +26 -0
- data/app/views/json/main_theme/theme.erb +7 -0
- data/app/views/json/theme.erb +38 -0
- data/app/views/json/wp_version/version.erb +8 -0
- data/bin/wpscan +15 -0
- data/coverage/assets/0.10.0/application.css +799 -0
- data/coverage/assets/0.10.0/application.js +1707 -0
- data/coverage/assets/0.10.0/colorbox/border.png +0 -0
- data/coverage/assets/0.10.0/colorbox/controls.png +0 -0
- data/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
- data/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
- data/coverage/assets/0.10.0/favicon_green.png +0 -0
- data/coverage/assets/0.10.0/favicon_red.png +0 -0
- data/coverage/assets/0.10.0/favicon_yellow.png +0 -0
- data/coverage/assets/0.10.0/loading.gif +0 -0
- data/coverage/assets/0.10.0/magnify.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/index.html +27510 -0
- data/lib/wpscan.rb +44 -0
- data/lib/wpscan/browser.rb +16 -0
- data/lib/wpscan/controller.rb +8 -0
- data/lib/wpscan/controllers.rb +8 -0
- data/lib/wpscan/db.rb +28 -0
- data/lib/wpscan/db/dynamic_finders.rb +63 -0
- data/lib/wpscan/db/plugin.rb +11 -0
- data/lib/wpscan/db/plugins.rb +11 -0
- data/lib/wpscan/db/schema.rb +39 -0
- data/lib/wpscan/db/theme.rb +11 -0
- data/lib/wpscan/db/themes.rb +11 -0
- data/lib/wpscan/db/updater.rb +148 -0
- data/lib/wpscan/db/wp_item.rb +18 -0
- data/lib/wpscan/db/wp_items.rb +21 -0
- data/lib/wpscan/db/wp_version.rb +11 -0
- data/lib/wpscan/errors/http.rb +34 -0
- data/lib/wpscan/errors/update.rb +8 -0
- data/lib/wpscan/errors/wordpress.rb +22 -0
- data/lib/wpscan/finders.rb +14 -0
- data/lib/wpscan/finders/finder/plugin_version/comments.rb +25 -0
- data/lib/wpscan/finders/finder/wp_version/smart_url_checker.rb +23 -0
- data/lib/wpscan/helper.rb +6 -0
- data/lib/wpscan/references.rb +31 -0
- data/lib/wpscan/target.rb +81 -0
- data/lib/wpscan/target/platform/wordpress.rb +74 -0
- data/lib/wpscan/target/platform/wordpress/custom_directories.rb +93 -0
- data/lib/wpscan/version.rb +4 -0
- data/lib/wpscan/vulnerability.rb +25 -0
- data/lib/wpscan/vulnerable.rb +10 -0
- data/wpscan-v3.sublime-project +8 -0
- data/wpscan-v3.sublime-workspace +895 -0
- data/wpscan.gemspec +55 -0
- metadata +419 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
module WpVersion
|
|
4
|
+
# Atom Generator Version Finder
|
|
5
|
+
class AtomGenerator < CMSScanner::Finders::Finder
|
|
6
|
+
include Finder::WpVersion::SmartURLChecker
|
|
7
|
+
|
|
8
|
+
def process_urls(urls, _opts = {})
|
|
9
|
+
found = Findings.new
|
|
10
|
+
|
|
11
|
+
urls.each do |url|
|
|
12
|
+
res = Browser.get_and_follow_location(url)
|
|
13
|
+
|
|
14
|
+
res.html.css('generator').each do |node|
|
|
15
|
+
next unless node.text.to_s.strip.casecmp('wordpress').zero?
|
|
16
|
+
|
|
17
|
+
found << create_version(
|
|
18
|
+
node['version'],
|
|
19
|
+
found_by: found_by,
|
|
20
|
+
entries: ["#{res.effective_url}, #{node}"]
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
found
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def passive_urls_xpath
|
|
29
|
+
'//link[@rel="alternate" and @type="application/atom+xml"]'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def aggressive_urls(_opts = {})
|
|
33
|
+
%w(feed/atom/ ?feed=atom).reduce([]) do |a, uri|
|
|
34
|
+
a << target.url(uri)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
module WpVersion
|
|
4
|
+
# Meta Generator Version Finder
|
|
5
|
+
class MetaGenerator < CMSScanner::Finders::Finder
|
|
6
|
+
# @return [ WpVersion ]
|
|
7
|
+
def passive(_opts = {})
|
|
8
|
+
target.homepage_res.html.css('meta[name="generator"]').each do |node|
|
|
9
|
+
next unless node.attribute('content').to_s =~ /wordpress ([0-9\.]+)/i
|
|
10
|
+
|
|
11
|
+
number = Regexp.last_match(1)
|
|
12
|
+
|
|
13
|
+
next unless WPScan::WpVersion.valid?(number)
|
|
14
|
+
|
|
15
|
+
return WPScan::WpVersion.new(
|
|
16
|
+
number,
|
|
17
|
+
found_by: 'Meta Generator (Passive detection)',
|
|
18
|
+
confidence: 80,
|
|
19
|
+
interesting_entries: ["#{target.url}, Match: '#{node}'"]
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
module WpVersion
|
|
4
|
+
# Sitemap Generator Version Finder
|
|
5
|
+
class OpmlGenerator < CMSScanner::Finders::Finder
|
|
6
|
+
# @return [ WpVersion ]
|
|
7
|
+
def aggressive(_opts = {})
|
|
8
|
+
target.comments_from_page(%r{\Agenerator="wordpress/([^"]+)"\z}i, 'wp-links-opml.php') do |match, node|
|
|
9
|
+
next unless WPScan::WpVersion.valid?(match[1])
|
|
10
|
+
|
|
11
|
+
return WPScan::WpVersion.new(
|
|
12
|
+
match[1],
|
|
13
|
+
found_by: 'OPML Generator (Aggressive Detection)',
|
|
14
|
+
confidence: 80,
|
|
15
|
+
interesting_entries: ["#{target.url('wp-links-opml.php')}, Match: '#{node}'"]
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
module WpVersion
|
|
4
|
+
# RDF Generator Version Finder
|
|
5
|
+
class RDFGenerator < CMSScanner::Finders::Finder
|
|
6
|
+
include Finder::WpVersion::SmartURLChecker
|
|
7
|
+
|
|
8
|
+
def process_urls(urls, _opts = {})
|
|
9
|
+
found = Findings.new
|
|
10
|
+
|
|
11
|
+
urls.each do |url|
|
|
12
|
+
res = Browser.get_and_follow_location(url)
|
|
13
|
+
|
|
14
|
+
res.html.xpath('//generatoragent').each do |node|
|
|
15
|
+
next unless node['rdf:resource'] =~ %r{\Ahttps?://wordpress\.(?:[a-z.]+)/\?v=(.*)\z}i
|
|
16
|
+
|
|
17
|
+
found << create_version(
|
|
18
|
+
Regexp.last_match[1],
|
|
19
|
+
found_by: found_by,
|
|
20
|
+
entries: ["#{res.effective_url}, #{node}"]
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
found
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def passive_urls_xpath
|
|
29
|
+
'//a[contains(@href, "rdf")]'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def aggressive_urls(_opts = {})
|
|
33
|
+
[target.url('feed/rdf/')]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
module WpVersion
|
|
4
|
+
# Readme Version Finder
|
|
5
|
+
class Readme < CMSScanner::Finders::Finder
|
|
6
|
+
# @return [ WpVersion ]
|
|
7
|
+
def aggressive(_opts = {})
|
|
8
|
+
readme_url = target.url('readme.html') # Maybe move this into the Target ?
|
|
9
|
+
|
|
10
|
+
node = Browser.get(readme_url).html.css('h1#logo').last
|
|
11
|
+
|
|
12
|
+
return unless node && node.text.to_s.strip =~ /\AVersion (.*)\z/i
|
|
13
|
+
|
|
14
|
+
number = Regexp.last_match(1)
|
|
15
|
+
|
|
16
|
+
return unless WPScan::WpVersion.valid?(number)
|
|
17
|
+
|
|
18
|
+
WPScan::WpVersion.new(
|
|
19
|
+
number,
|
|
20
|
+
found_by: 'Readme (Aggressive Detection)',
|
|
21
|
+
confidence: 90,
|
|
22
|
+
interesting_entries: ["#{readme_url}, Match: '#{node.text.to_s.strip}'"]
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
module WpVersion
|
|
4
|
+
# RSS Generator Version Finder
|
|
5
|
+
class RSSGenerator < CMSScanner::Finders::Finder
|
|
6
|
+
include Finder::WpVersion::SmartURLChecker
|
|
7
|
+
|
|
8
|
+
def process_urls(urls, _opts = {})
|
|
9
|
+
found = Findings.new
|
|
10
|
+
|
|
11
|
+
urls.each do |url|
|
|
12
|
+
res = Browser.get_and_follow_location(url)
|
|
13
|
+
|
|
14
|
+
res.html.xpath('//comment()[contains(., "wordpress")] | //generator').each do |node|
|
|
15
|
+
node_text = node.text.to_s.strip
|
|
16
|
+
|
|
17
|
+
next unless node_text =~ %r{\Ahttps?://wordpress\.(?:[a-z]+)/\?v=(.*)\z}i ||
|
|
18
|
+
node_text =~ %r{\Agenerator="wordpress/([^"]+)"\z}i
|
|
19
|
+
|
|
20
|
+
found << create_version(
|
|
21
|
+
Regexp.last_match[1],
|
|
22
|
+
found_by: found_by,
|
|
23
|
+
entries: ["#{res.effective_url}, #{node}"]
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
found
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def passive_urls_xpath
|
|
32
|
+
'//link[@rel="alternate" and @type="application/rss+xml"]'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def aggressive_urls(_opts = {})
|
|
36
|
+
%w(feed/ comments/feed/ feed/rss/ feed/rss2/).reduce([]) do |a, uri|
|
|
37
|
+
a << target.url(uri)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
module WpVersion
|
|
4
|
+
# Sitemap Generator Version Finder
|
|
5
|
+
class SitemapGenerator < CMSScanner::Finders::Finder
|
|
6
|
+
# @return [ WpVersion ]
|
|
7
|
+
def aggressive(_opts = {})
|
|
8
|
+
target.comments_from_page(%r{\Agenerator="wordpress/([^"]+)"\z}i, 'sitemap.xml') do |match, node|
|
|
9
|
+
next unless WPScan::WpVersion.valid?(match[1])
|
|
10
|
+
|
|
11
|
+
return WPScan::WpVersion.new(
|
|
12
|
+
match[1],
|
|
13
|
+
found_by: 'Sitemap Generator (Aggressive Detection)',
|
|
14
|
+
confidence: 80,
|
|
15
|
+
interesting_entries: ["#{target.url('sitemap.xml')}, #{node}"]
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
module WpVersion
|
|
4
|
+
# Stylesheets Version Finder
|
|
5
|
+
class Stylesheets < CMSScanner::Finders::Finder
|
|
6
|
+
# @return [ WpVersion ]
|
|
7
|
+
def passive(_opts = {})
|
|
8
|
+
found = []
|
|
9
|
+
|
|
10
|
+
scan_page(target.homepage_url).each do |version_number, occurences|
|
|
11
|
+
next unless WPScan::WpVersion.valid?(version_number) # Skip invalid versions
|
|
12
|
+
|
|
13
|
+
found << WPScan::WpVersion.new(
|
|
14
|
+
version_number,
|
|
15
|
+
found_by: 'Stylesheet Numbers (Passive Detection)',
|
|
16
|
+
confidence: 5 * occurences,
|
|
17
|
+
interesting_entries: [target.homepage_url]
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
found
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
# TODO: use target.in_scope_urls to get the URLs
|
|
27
|
+
# @param [ String ] url
|
|
28
|
+
#
|
|
29
|
+
# @return [ Hash ]
|
|
30
|
+
def scan_page(url)
|
|
31
|
+
found = {}
|
|
32
|
+
pattern = /\bver=([0-9\.]+)/i
|
|
33
|
+
|
|
34
|
+
Browser.get(url).html.css('link,script').each do |tag|
|
|
35
|
+
%w(href src).each do |attribute|
|
|
36
|
+
attr_value = tag.attribute(attribute).to_s
|
|
37
|
+
|
|
38
|
+
next if attr_value.nil? || attr_value.empty?
|
|
39
|
+
|
|
40
|
+
uri = Addressable::URI.parse(attr_value)
|
|
41
|
+
next unless uri.query && uri.query.match(pattern)
|
|
42
|
+
|
|
43
|
+
version = Regexp.last_match[1].to_s
|
|
44
|
+
|
|
45
|
+
found[version] ||= 0
|
|
46
|
+
found[version] += 1
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
found
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
module Finders
|
|
3
|
+
module WpVersion
|
|
4
|
+
# Unique Fingerprinting Version Finder
|
|
5
|
+
class UniqueFingerprinting < CMSScanner::Finders::Finder
|
|
6
|
+
include CMSScanner::Finders::Finder::Fingerprinter
|
|
7
|
+
|
|
8
|
+
QUERY = 'SELECT md5_hash, path_id, version_id, ' \
|
|
9
|
+
'versions.number AS version,' \
|
|
10
|
+
'paths.value AS path ' \
|
|
11
|
+
'FROM fingerprints ' \
|
|
12
|
+
'LEFT JOIN versions ON version_id = versions.id ' \
|
|
13
|
+
'LEFT JOIN paths on path_id = paths.id ' \
|
|
14
|
+
'WHERE md5_hash IN ' \
|
|
15
|
+
'(SELECT md5_hash FROM fingerprints GROUP BY md5_hash HAVING COUNT(*) = 1) ' \
|
|
16
|
+
'ORDER BY version DESC'.freeze
|
|
17
|
+
|
|
18
|
+
# @return [ WpVersion ]
|
|
19
|
+
def aggressive(opts = {})
|
|
20
|
+
fingerprint(unique_fingerprints, opts) do |version_number, url, md5sum|
|
|
21
|
+
hydra.abort
|
|
22
|
+
progress_bar.finish
|
|
23
|
+
|
|
24
|
+
return WPScan::WpVersion.new(
|
|
25
|
+
version_number,
|
|
26
|
+
found_by: 'Unique Fingerprinting (Aggressive Detection)',
|
|
27
|
+
confidence: 100,
|
|
28
|
+
interesting_entries: ["#{url} md5sum is #{md5sum}"]
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [ Hash ] The unique fingerprints across all versions in the DB
|
|
35
|
+
#
|
|
36
|
+
# Format returned:
|
|
37
|
+
# {
|
|
38
|
+
# file_path_1: {
|
|
39
|
+
# md5_hash_1: version_1,
|
|
40
|
+
# md5_hash_2: version_2
|
|
41
|
+
# },
|
|
42
|
+
# file_path_2: {
|
|
43
|
+
# md5_hash_3: version_1,
|
|
44
|
+
# md5_hash_4: version_3
|
|
45
|
+
# }
|
|
46
|
+
# }
|
|
47
|
+
def unique_fingerprints
|
|
48
|
+
fingerprints = {}
|
|
49
|
+
|
|
50
|
+
repository(:default).adapter.select(QUERY).each do |f|
|
|
51
|
+
fingerprints[f.path] ||= {}
|
|
52
|
+
fingerprints[f.path][f.md5_hash] = f.version
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
fingerprints
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def create_progress_bar(opts = {})
|
|
59
|
+
super(opts.merge(title: 'Fingerprinting the version -'))
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data/app/models.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require_relative 'models/interesting_finding'
|
|
2
|
+
require_relative 'models/wp_version'
|
|
3
|
+
require_relative 'models/xml_rpc'
|
|
4
|
+
require_relative 'models/wp_item'
|
|
5
|
+
require_relative 'models/timthumb'
|
|
6
|
+
require_relative 'models/media'
|
|
7
|
+
require_relative 'models/user'
|
|
8
|
+
require_relative 'models/plugin'
|
|
9
|
+
require_relative 'models/theme'
|
|
10
|
+
require_relative 'models/config_backup'
|
data/app/models/media.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
# WordPress Plugin
|
|
3
|
+
class Plugin < WpItem
|
|
4
|
+
# See WpItem
|
|
5
|
+
def initialize(name, target, opts = {})
|
|
6
|
+
super(name, target, opts)
|
|
7
|
+
|
|
8
|
+
@uri = Addressable::URI.parse(target.url("wp-content/plugins/#{name}/"))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @return [ JSON ]
|
|
12
|
+
def db_data
|
|
13
|
+
DB::Plugin.db_data(name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param [ Hash ] opts
|
|
17
|
+
#
|
|
18
|
+
# @return [ WPScan::Version, false ]
|
|
19
|
+
def version(opts = {})
|
|
20
|
+
@version = Finders::PluginVersion::Base.find(self, detection_opts.merge(opts)) if @version.nil?
|
|
21
|
+
|
|
22
|
+
@version
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/app/models/theme.rb
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
module WPScan
|
|
2
|
+
# WordPress Theme
|
|
3
|
+
class Theme < WpItem
|
|
4
|
+
attr_reader :style_url, :style_name, :style_uri, :author, :author_uri, :template, :description,
|
|
5
|
+
:license, :license_uri, :tags, :text_domain
|
|
6
|
+
|
|
7
|
+
# See WpItem
|
|
8
|
+
def initialize(name, target, opts = {})
|
|
9
|
+
super(name, target, opts)
|
|
10
|
+
|
|
11
|
+
@uri = Addressable::URI.parse(target.url("wp-content/themes/#{name}/"))
|
|
12
|
+
@style_url = opts[:style_url] || url('style.css')
|
|
13
|
+
|
|
14
|
+
parse_style
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [ JSON ]
|
|
18
|
+
def db_data
|
|
19
|
+
DB::Theme.db_data(name)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param [ Hash ] opts
|
|
23
|
+
#
|
|
24
|
+
# @return [ WPScan::Version, false ]
|
|
25
|
+
def version(opts = {})
|
|
26
|
+
@version = Finders::ThemeVersion::Base.find(self, detection_opts.merge(opts)) if @version.nil?
|
|
27
|
+
|
|
28
|
+
@version
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [ Theme ]
|
|
32
|
+
def parent_theme
|
|
33
|
+
return unless template
|
|
34
|
+
return unless style_body =~ /^@import\surl\(["']?([^"'\)]+)["']?\);\s*$/i
|
|
35
|
+
|
|
36
|
+
opts = detection_opts.merge(
|
|
37
|
+
style_url: url(Regexp.last_match[1]),
|
|
38
|
+
found_by: 'Parent Themes (Passive Detection)',
|
|
39
|
+
confidence: 100
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
self.class.new(template, target, opts)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param [ Integer ] depth
|
|
46
|
+
#
|
|
47
|
+
# @retun [ Array<Theme> ]
|
|
48
|
+
def parent_themes(depth = 3)
|
|
49
|
+
theme = self
|
|
50
|
+
found = []
|
|
51
|
+
|
|
52
|
+
(1..depth).each do |_|
|
|
53
|
+
parent = theme.parent_theme
|
|
54
|
+
|
|
55
|
+
break unless parent
|
|
56
|
+
|
|
57
|
+
found << parent
|
|
58
|
+
theme = parent
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
found
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def style_body
|
|
65
|
+
@style_body ||= Browser.get(style_url).body
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def parse_style
|
|
69
|
+
{
|
|
70
|
+
style_name: 'Theme Name',
|
|
71
|
+
style_uri: 'Theme URI',
|
|
72
|
+
author: 'Author',
|
|
73
|
+
author_uri: 'Author URI',
|
|
74
|
+
template: 'Template',
|
|
75
|
+
description: 'Description',
|
|
76
|
+
license: 'License',
|
|
77
|
+
license_uri: 'License URI',
|
|
78
|
+
tags: 'Tags',
|
|
79
|
+
text_domain: 'Text Domain'
|
|
80
|
+
}.each do |attribute, tag|
|
|
81
|
+
instance_variable_set(:"@#{attribute}", parse_style_tag(style_body, tag))
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @param [ String ] bofy
|
|
86
|
+
# @param [ String ] tag
|
|
87
|
+
#
|
|
88
|
+
# @return [ String ]
|
|
89
|
+
def parse_style_tag(body, tag)
|
|
90
|
+
value = body[/^\s*#{Regexp.escape(tag)}:[\t ]*([^\r\n]+)/i, 1]
|
|
91
|
+
|
|
92
|
+
value && !value.strip.empty? ? value.strip : nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ==(other)
|
|
96
|
+
super(other) && style_url == other.style_url
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|