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.
Files changed (180) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile.lock +139 -0
  3. data/LICENSE +74 -0
  4. data/README.md +146 -0
  5. data/app/app.rb +3 -0
  6. data/app/controllers.rb +6 -0
  7. data/app/controllers/brute_force.rb +126 -0
  8. data/app/controllers/core.rb +104 -0
  9. data/app/controllers/custom_directories.rb +23 -0
  10. data/app/controllers/enumeration.rb +53 -0
  11. data/app/controllers/enumeration/cli_options.rb +126 -0
  12. data/app/controllers/enumeration/enum_methods.rb +157 -0
  13. data/app/controllers/main_theme.rb +27 -0
  14. data/app/controllers/wp_version.rb +30 -0
  15. data/app/finders.rb +13 -0
  16. data/app/finders/config_backups.rb +17 -0
  17. data/app/finders/config_backups/known_filenames.rb +46 -0
  18. data/app/finders/interesting_findings.rb +33 -0
  19. data/app/finders/interesting_findings/backup_db.rb +25 -0
  20. data/app/finders/interesting_findings/debug_log.rb +20 -0
  21. data/app/finders/interesting_findings/duplicator_installer_log.rb +23 -0
  22. data/app/finders/interesting_findings/full_path_disclosure.rb +23 -0
  23. data/app/finders/interesting_findings/mu_plugins.rb +48 -0
  24. data/app/finders/interesting_findings/multisite.rb +29 -0
  25. data/app/finders/interesting_findings/readme.rb +26 -0
  26. data/app/finders/interesting_findings/registration.rb +31 -0
  27. data/app/finders/interesting_findings/tmm_db_migrate.rb +24 -0
  28. data/app/finders/interesting_findings/upload_directory_listing.rb +24 -0
  29. data/app/finders/interesting_findings/upload_sql_dump.rb +28 -0
  30. data/app/finders/main_theme.rb +22 -0
  31. data/app/finders/main_theme/css_style.rb +43 -0
  32. data/app/finders/main_theme/urls_in_homepage.rb +25 -0
  33. data/app/finders/main_theme/woo_framework_meta_generator.rb +22 -0
  34. data/app/finders/medias.rb +17 -0
  35. data/app/finders/medias/attachment_brute_forcing.rb +44 -0
  36. data/app/finders/plugin_version.rb +44 -0
  37. data/app/finders/plugin_version/layer_slider/translation_file.rb +40 -0
  38. data/app/finders/plugin_version/readme.rb +79 -0
  39. data/app/finders/plugin_version/revslider/release_log.rb +35 -0
  40. data/app/finders/plugin_version/sitepress_multilingual_cms/meta_generator.rb +27 -0
  41. data/app/finders/plugin_version/sitepress_multilingual_cms/version_parameter.rb +31 -0
  42. data/app/finders/plugin_version/w3_total_cache/headers.rb +28 -0
  43. data/app/finders/plugins.rb +24 -0
  44. data/app/finders/plugins/comments.rb +31 -0
  45. data/app/finders/plugins/headers.rb +36 -0
  46. data/app/finders/plugins/known_locations.rb +48 -0
  47. data/app/finders/plugins/urls_in_homepage.rb +29 -0
  48. data/app/finders/theme_version.rb +41 -0
  49. data/app/finders/theme_version/style.rb +43 -0
  50. data/app/finders/theme_version/woo_framework_meta_generator.rb +19 -0
  51. data/app/finders/themes.rb +20 -0
  52. data/app/finders/themes/known_locations.rb +48 -0
  53. data/app/finders/themes/urls_in_homepage.rb +23 -0
  54. data/app/finders/timthumb_version.rb +17 -0
  55. data/app/finders/timthumb_version/bad_request.rb +21 -0
  56. data/app/finders/timthumbs.rb +17 -0
  57. data/app/finders/timthumbs/known_locations.rb +56 -0
  58. data/app/finders/users.rb +24 -0
  59. data/app/finders/users/author_id_brute_forcing.rb +111 -0
  60. data/app/finders/users/author_posts.rb +61 -0
  61. data/app/finders/users/login_error_messages.rb +50 -0
  62. data/app/finders/users/wp_json_api.rb +31 -0
  63. data/app/finders/wp_items.rb +1 -0
  64. data/app/finders/wp_items/urls_in_homepage.rb +68 -0
  65. data/app/finders/wp_version.rb +34 -0
  66. data/app/finders/wp_version/atom_generator.rb +40 -0
  67. data/app/finders/wp_version/meta_generator.rb +27 -0
  68. data/app/finders/wp_version/opml_generator.rb +23 -0
  69. data/app/finders/wp_version/rdf_generator.rb +38 -0
  70. data/app/finders/wp_version/readme.rb +28 -0
  71. data/app/finders/wp_version/rss_generator.rb +43 -0
  72. data/app/finders/wp_version/sitemap_generator.rb +23 -0
  73. data/app/finders/wp_version/stylesheets.rb +55 -0
  74. data/app/finders/wp_version/unique_fingerprinting.rb +64 -0
  75. data/app/models.rb +10 -0
  76. data/app/models/config_backup.rb +5 -0
  77. data/app/models/interesting_finding.rb +6 -0
  78. data/app/models/media.rb +5 -0
  79. data/app/models/plugin.rb +25 -0
  80. data/app/models/theme.rb +99 -0
  81. data/app/models/timthumb.rb +74 -0
  82. data/app/models/user.rb +31 -0
  83. data/app/models/wp_item.rb +142 -0
  84. data/app/models/wp_version.rb +49 -0
  85. data/app/models/xml_rpc.rb +19 -0
  86. data/app/views/cli/brute_force/error.erb +1 -0
  87. data/app/views/cli/brute_force/found.erb +2 -0
  88. data/app/views/cli/brute_force/users.erb +9 -0
  89. data/app/views/cli/core/banner.erb +14 -0
  90. data/app/views/cli/core/db_update_finished.erb +8 -0
  91. data/app/views/cli/core/db_update_started.erb +1 -0
  92. data/app/views/cli/core/not_fully_configured.erb +1 -0
  93. data/app/views/cli/enumeration/config_backups.erb +11 -0
  94. data/app/views/cli/enumeration/medias.erb +11 -0
  95. data/app/views/cli/enumeration/plugins.erb +35 -0
  96. data/app/views/cli/enumeration/themes.erb +11 -0
  97. data/app/views/cli/enumeration/timthumbs.erb +18 -0
  98. data/app/views/cli/enumeration/users.erb +11 -0
  99. data/app/views/cli/finding.erb +32 -0
  100. data/app/views/cli/info.erb +1 -0
  101. data/app/views/cli/main_theme/theme.erb +6 -0
  102. data/app/views/cli/notice.erb +1 -0
  103. data/app/views/cli/theme.erb +64 -0
  104. data/app/views/cli/usage.erb +3 -0
  105. data/app/views/cli/vulnerability.erb +14 -0
  106. data/app/views/cli/wp_version/version.erb +6 -0
  107. data/app/views/json/brute_force/users.erb +10 -0
  108. data/app/views/json/core/banner.erb +12 -0
  109. data/app/views/json/core/db_update_finished.erb +2 -0
  110. data/app/views/json/core/db_update_started.erb +1 -0
  111. data/app/views/json/core/not_fully_configured.erb +1 -0
  112. data/app/views/json/enumeration/config_backups.erb +10 -0
  113. data/app/views/json/enumeration/medias.erb +10 -0
  114. data/app/views/json/enumeration/plugins.erb +25 -0
  115. data/app/views/json/enumeration/themes.erb +10 -0
  116. data/app/views/json/enumeration/timthumbs.erb +19 -0
  117. data/app/views/json/enumeration/users.erb +11 -0
  118. data/app/views/json/finding.erb +26 -0
  119. data/app/views/json/main_theme/theme.erb +7 -0
  120. data/app/views/json/theme.erb +38 -0
  121. data/app/views/json/wp_version/version.erb +8 -0
  122. data/bin/wpscan +15 -0
  123. data/coverage/assets/0.10.0/application.css +799 -0
  124. data/coverage/assets/0.10.0/application.js +1707 -0
  125. data/coverage/assets/0.10.0/colorbox/border.png +0 -0
  126. data/coverage/assets/0.10.0/colorbox/controls.png +0 -0
  127. data/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
  128. data/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
  129. data/coverage/assets/0.10.0/favicon_green.png +0 -0
  130. data/coverage/assets/0.10.0/favicon_red.png +0 -0
  131. data/coverage/assets/0.10.0/favicon_yellow.png +0 -0
  132. data/coverage/assets/0.10.0/loading.gif +0 -0
  133. data/coverage/assets/0.10.0/magnify.png +0 -0
  134. data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  135. data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  136. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  137. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  138. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  139. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  140. data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  141. data/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  142. data/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
  143. data/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  144. data/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
  145. data/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
  146. data/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  147. data/coverage/index.html +27510 -0
  148. data/lib/wpscan.rb +44 -0
  149. data/lib/wpscan/browser.rb +16 -0
  150. data/lib/wpscan/controller.rb +8 -0
  151. data/lib/wpscan/controllers.rb +8 -0
  152. data/lib/wpscan/db.rb +28 -0
  153. data/lib/wpscan/db/dynamic_finders.rb +63 -0
  154. data/lib/wpscan/db/plugin.rb +11 -0
  155. data/lib/wpscan/db/plugins.rb +11 -0
  156. data/lib/wpscan/db/schema.rb +39 -0
  157. data/lib/wpscan/db/theme.rb +11 -0
  158. data/lib/wpscan/db/themes.rb +11 -0
  159. data/lib/wpscan/db/updater.rb +148 -0
  160. data/lib/wpscan/db/wp_item.rb +18 -0
  161. data/lib/wpscan/db/wp_items.rb +21 -0
  162. data/lib/wpscan/db/wp_version.rb +11 -0
  163. data/lib/wpscan/errors/http.rb +34 -0
  164. data/lib/wpscan/errors/update.rb +8 -0
  165. data/lib/wpscan/errors/wordpress.rb +22 -0
  166. data/lib/wpscan/finders.rb +14 -0
  167. data/lib/wpscan/finders/finder/plugin_version/comments.rb +25 -0
  168. data/lib/wpscan/finders/finder/wp_version/smart_url_checker.rb +23 -0
  169. data/lib/wpscan/helper.rb +6 -0
  170. data/lib/wpscan/references.rb +31 -0
  171. data/lib/wpscan/target.rb +81 -0
  172. data/lib/wpscan/target/platform/wordpress.rb +74 -0
  173. data/lib/wpscan/target/platform/wordpress/custom_directories.rb +93 -0
  174. data/lib/wpscan/version.rb +4 -0
  175. data/lib/wpscan/vulnerability.rb +25 -0
  176. data/lib/wpscan/vulnerable.rb +10 -0
  177. data/wpscan-v3.sublime-project +8 -0
  178. data/wpscan-v3.sublime-workspace +895 -0
  179. data/wpscan.gemspec +55 -0
  180. 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
@@ -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'
@@ -0,0 +1,5 @@
1
+ module WPScan
2
+ # Config Backup
3
+ class ConfigBackup < InterestingFinding
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ module WPScan
2
+ # Custom class to include the WPScan::References module
3
+ class InterestingFinding < CMSScanner::InterestingFinding
4
+ include References
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module WPScan
2
+ # Media
3
+ class Media < InterestingFinding
4
+ end
5
+ end
@@ -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
@@ -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