wpscan 3.0.8 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +1 -1
  4. data/app/controllers.rb +1 -0
  5. data/app/controllers/aliases.rb +12 -0
  6. data/app/controllers/core.rb +3 -5
  7. data/app/controllers/enumeration.rb +2 -28
  8. data/app/controllers/enumeration/enum_methods.rb +12 -2
  9. data/app/controllers/wp_version.rb +4 -0
  10. data/app/finders/main_theme/css_style.rb +2 -2
  11. data/app/finders/main_theme/urls_in_homepage.rb +3 -3
  12. data/app/finders/plugin_version.rb +1 -8
  13. data/app/finders/plugins.rb +13 -4
  14. data/app/finders/plugins/body_pattern.rb +27 -0
  15. data/app/finders/plugins/comment.rb +31 -0
  16. data/app/finders/plugins/config_parser.rb +31 -0
  17. data/app/finders/plugins/header_pattern.rb +41 -0
  18. data/app/finders/plugins/javascript_var.rb +29 -0
  19. data/app/finders/plugins/known_locations.rb +5 -5
  20. data/app/finders/plugins/query_parameter.rb +25 -0
  21. data/app/finders/plugins/urls_in_homepage.rb +4 -8
  22. data/app/finders/plugins/xpath.rb +29 -0
  23. data/app/finders/theme_version.rb +1 -1
  24. data/app/finders/theme_version/woo_framework_meta_generator.rb +2 -2
  25. data/app/finders/themes/known_locations.rb +5 -5
  26. data/app/finders/themes/urls_in_homepage.rb +2 -2
  27. data/app/finders/users/login_error_messages.rb +1 -4
  28. data/app/finders/users/wp_json_api.rb +2 -2
  29. data/app/finders/wp_items/urls_in_homepage.rb +1 -1
  30. data/app/finders/wp_version.rb +21 -18
  31. data/app/models/plugin.rb +4 -4
  32. data/app/models/theme.rb +6 -6
  33. data/app/models/timthumb.rb +1 -3
  34. data/app/models/wp_item.rb +15 -15
  35. data/app/views/json/enumeration/plugins.erb +1 -1
  36. data/app/views/json/enumeration/themes.erb +1 -1
  37. data/app/views/json/wp_item.erb +1 -1
  38. data/bin/wpscan +2 -1
  39. data/lib/wpscan/db.rb +14 -10
  40. data/lib/wpscan/db/dynamic_finders/base.rb +41 -0
  41. data/lib/wpscan/db/dynamic_finders/plugin.rb +111 -0
  42. data/lib/wpscan/db/dynamic_finders/theme.rb +16 -0
  43. data/lib/wpscan/db/dynamic_finders/wordpress.rb +75 -0
  44. data/lib/wpscan/db/updater.rb +2 -2
  45. data/lib/wpscan/finders.rb +13 -1
  46. data/lib/wpscan/finders/dynamic_finder/finder.rb +66 -0
  47. data/lib/wpscan/finders/dynamic_finder/version/body_pattern.rb +28 -0
  48. data/lib/wpscan/finders/dynamic_finder/version/comment.rb +16 -0
  49. data/lib/wpscan/finders/dynamic_finder/version/config_parser.rb +52 -0
  50. data/lib/wpscan/finders/dynamic_finder/version/finder.rb +29 -0
  51. data/lib/wpscan/finders/dynamic_finder/version/header_pattern.rb +28 -0
  52. data/lib/wpscan/finders/dynamic_finder/version/javascript_var.rb +56 -0
  53. data/lib/wpscan/finders/dynamic_finder/version/query_parameter.rb +62 -0
  54. data/lib/wpscan/finders/dynamic_finder/version/xpath.rb +34 -0
  55. data/lib/wpscan/finders/dynamic_finder/wp_item_version.rb +42 -0
  56. data/lib/wpscan/finders/dynamic_finder/wp_items/finder.rb +96 -0
  57. data/lib/wpscan/finders/dynamic_finder/wp_version.rb +60 -0
  58. data/lib/wpscan/helper.rb +11 -0
  59. data/lib/wpscan/target/platform/wordpress/custom_directories.rb +16 -1
  60. data/lib/wpscan/version.rb +1 -1
  61. metadata +32 -24
  62. data/app/finders/plugin_version/layer_slider/translation_file.rb +0 -40
  63. data/app/finders/plugin_version/revslider/release_log.rb +0 -35
  64. data/app/finders/plugin_version/shareaholic/meta_tag.rb +0 -27
  65. data/app/finders/plugin_version/sitepress_multilingual_cms/meta_generator.rb +0 -27
  66. data/app/finders/plugin_version/sitepress_multilingual_cms/version_parameter.rb +0 -31
  67. data/app/finders/plugin_version/w3_total_cache/headers.rb +0 -28
  68. data/app/finders/plugins/comments.rb +0 -31
  69. data/app/finders/plugins/headers.rb +0 -36
  70. data/app/finders/wp_version/homepage_stylesheet_numbers.rb +0 -59
  71. data/app/finders/wp_version/install_stylesheet_numbers.rb +0 -16
  72. data/app/finders/wp_version/meta_generator.rb +0 -27
  73. data/app/finders/wp_version/opml_generator.rb +0 -23
  74. data/app/finders/wp_version/sitemap_generator.rb +0 -23
  75. data/app/finders/wp_version/upgrade_stylesheet_numbers.rb +0 -13
  76. data/lib/wpscan/db/dynamic_finders.rb +0 -55
  77. data/lib/wpscan/finders/finder/plugin_version/comments.rb +0 -27
@@ -2,6 +2,8 @@ module WPScan
2
2
  module Finders
3
3
  module Plugins
4
4
  # URLs In Homepage Finder
5
+ # Typically, the items detected from URLs like
6
+ # /wp-content/plugins/<slug>/
5
7
  class UrlsInHomepage < CMSScanner::Finders::Finder
6
8
  include WpItems::URLsInHomepage
7
9
 
@@ -11,14 +13,8 @@ module WPScan
11
13
  def passive(opts = {})
12
14
  found = []
13
15
 
14
- (items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.each do |name|
15
- found << Plugin.new(name, target, opts.merge(found_by: found_by, confidence: 80))
16
- end
17
-
18
- DB::DynamicPluginFinders.urls_in_page.each do |name, config|
19
- next unless target.homepage_res.html.xpath(config['xpath']).any?
20
-
21
- found << Plugin.new(name, target, opts.merge(found_by: found_by, confidence: 100))
16
+ (items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.each do |slug|
17
+ found << Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
22
18
  end
23
19
 
24
20
  found
@@ -0,0 +1,29 @@
1
+ module WPScan
2
+ module Finders
3
+ module Plugins
4
+ # Plugins finder from the Dynamic Finder 'Xpath'
5
+ class Xpath < WPScan::Finders::DynamicFinder::WpItems::Finder
6
+ DEFAULT_CONFIDENCE = 40
7
+
8
+ # @param [ Hash ] opts The options from the #passive, #aggressive methods
9
+ # @param [ Typhoeus::Response ] response
10
+ # @param [ String ] slug
11
+ # @param [ String ] klass
12
+ # @param [ Hash ] config The related dynamic finder config hash
13
+ #
14
+ # @return [ Plugin ] The detected plugin in the response, related to the config
15
+ def process_response(opts, response, slug, klass, config)
16
+ response.html.xpath(config['xpath']).each do |node|
17
+ next if config['pattern'] && !node.text.match(config['pattern'])
18
+
19
+ return Plugin.new(
20
+ slug,
21
+ target,
22
+ opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -21,7 +21,7 @@ module WPScan
21
21
  #
22
22
  # @param [ WPScan::Theme ] theme
23
23
  def load_specific_finders(theme)
24
- module_name = theme.classify_name.to_sym
24
+ module_name = theme.classify
25
25
 
26
26
  return unless Finders::ThemeVersion.constants.include?(module_name)
27
27
 
@@ -7,9 +7,9 @@ module WPScan
7
7
  #
8
8
  # @return [ Version ]
9
9
  def passive(_opts = {})
10
- return unless target.target.homepage_res.body =~ Finders::MainTheme::WooFrameworkMetaGenerator::PATTERN
10
+ return unless target.blog.homepage_res.body =~ Finders::MainTheme::WooFrameworkMetaGenerator::PATTERN
11
11
 
12
- return unless Regexp.last_match[1] == target.name
12
+ return unless Regexp.last_match[1] == target.slug
13
13
 
14
14
  WPScan::Version.new(Regexp.last_match[2], found_by: found_by, confidence: 80)
15
15
  end
@@ -12,12 +12,12 @@ module WPScan
12
12
  def aggressive(opts = {})
13
13
  found = []
14
14
 
15
- enumerate(target_urls(opts), opts) do |res, name|
15
+ enumerate(target_urls(opts), opts) do |res, slug|
16
16
  # TODO: follow the location (from enumerate()) and remove the 301 here ?
17
17
  # As a result, it might remove false positive due to redirection to the homepage
18
18
  next unless [200, 401, 403, 301].include?(res.code)
19
19
 
20
- found << WPScan::Theme.new(name, target, opts.merge(found_by: found_by, confidence: 80))
20
+ found << WPScan::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
21
21
  end
22
22
 
23
23
  found
@@ -28,12 +28,12 @@ module WPScan
28
28
  #
29
29
  # @return [ Hash ]
30
30
  def target_urls(opts = {})
31
- names = opts[:list] || DB::Themes.vulnerable_slugs
31
+ slugs = opts[:list] || DB::Themes.vulnerable_slugs
32
32
  urls = {}
33
33
  themes_url = target.url('wp-content/themes/')
34
34
 
35
- names.each do |name|
36
- urls["#{themes_url}#{URI.encode(name)}/"] = name
35
+ slugs.each do |slug|
36
+ urls["#{themes_url}#{URI.encode(slug)}/"] = slug
37
37
  end
38
38
 
39
39
  urls
@@ -11,8 +11,8 @@ module WPScan
11
11
  def passive(opts = {})
12
12
  found = []
13
13
 
14
- (items_from_links('themes') + items_from_codes('themes')).uniq.sort.each do |name|
15
- found << WPScan::Theme.new(name, target, opts.merge(found_by: found_by, confidence: 80))
14
+ (items_from_links('themes') + items_from_codes('themes')).uniq.sort.each do |slug|
15
+ found << WPScan::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
16
16
  end
17
17
 
18
18
  found
@@ -17,10 +17,7 @@ module WPScan
17
17
  found = []
18
18
 
19
19
  usernames(opts).each do |username|
20
- res = target.do_login(username, SecureRandom.hex[0, 8])
21
-
22
- return found unless res.code == 200
23
-
20
+ res = target.do_login(username, SecureRandom.hex[0, 8])
24
21
  error = res.html.css('div#login_error').text.strip
25
22
 
26
23
  return found if error.empty? # Protection plugin / error disabled
@@ -12,7 +12,7 @@ module WPScan
12
12
  def aggressive(_opts = {})
13
13
  found = []
14
14
 
15
- JSON.parse(Browser.get(api_url).body).each do |user|
15
+ JSON.parse(Browser.get(api_url).body)&.each do |user|
16
16
  found << WPScan::User.new(user['slug'],
17
17
  id: user['id'],
18
18
  found_by: found_by,
@@ -21,7 +21,7 @@ module WPScan
21
21
  end
22
22
 
23
23
  found
24
- rescue JSON::ParserError
24
+ rescue JSON::ParserError, TypeError
25
25
  found
26
26
  end
27
27
 
@@ -30,7 +30,7 @@ module WPScan
30
30
  code = tag.text.to_s
31
31
  next if code.empty?
32
32
 
33
- code.scan(item_code_pattern(type)).flatten.uniq.each { |name| found << name }
33
+ code.scan(item_code_pattern(type)).flatten.uniq.each { |slug| found << slug }
34
34
  end
35
35
 
36
36
  uniq ? found.uniq.sort : found.sort
@@ -1,17 +1,23 @@
1
- require_relative 'wp_version/meta_generator'
2
1
  require_relative 'wp_version/rss_generator'
3
2
  require_relative 'wp_version/atom_generator'
4
3
  require_relative 'wp_version/rdf_generator'
5
4
  require_relative 'wp_version/readme'
6
- require_relative 'wp_version/sitemap_generator'
7
- require_relative 'wp_version/opml_generator'
8
- require_relative 'wp_version/homepage_stylesheet_numbers'
9
- require_relative 'wp_version/install_stylesheet_numbers'
10
- require_relative 'wp_version/upgrade_stylesheet_numbers'
11
5
  require_relative 'wp_version/unique_fingerprinting'
12
6
 
13
7
  module WPScan
14
8
  module Finders
9
+ # Specific Finders container to filter the version detected
10
+ # and remove the one with low confidence to avoid false
11
+ # positive when there is not enought information to accurately
12
+ # determine it.
13
+ class WpVersionFinders < UniqueFinders
14
+ def filter_findings
15
+ best_finding = super
16
+
17
+ best_finding && best_finding.confidence >= 40 ? best_finding : false
18
+ end
19
+ end
20
+
15
21
  module WpVersion
16
22
  # Wp Version Finder
17
23
  class Base
@@ -19,18 +25,15 @@ module WPScan
19
25
 
20
26
  # @param [ WPScan::Target ] target
21
27
  def initialize(target)
22
- finders <<
23
- WpVersion::MetaGenerator.new(target) <<
24
- WpVersion::RSSGenerator.new(target) <<
25
- WpVersion::AtomGenerator.new(target) <<
26
- WpVersion::HomepageStylesheetNumbers.new(target) <<
27
- WpVersion::InstallStylesheetNumbers.new(target) <<
28
- WpVersion::UpgradeStylesheetNumbers.new(target) <<
29
- WpVersion::RDFGenerator.new(target) <<
30
- WpVersion::Readme.new(target) <<
31
- WpVersion::SitemapGenerator.new(target) <<
32
- WpVersion::OpmlGenerator.new(target) <<
33
- WpVersion::UniqueFingerprinting.new(target)
28
+ (WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
29
+ %w[RSSGenerator AtomGenerator RDFGenerator Readme UniqueFingerprinting]
30
+ ).each do |finder_name|
31
+ finders << WpVersion.const_get(finder_name.to_sym).new(target)
32
+ end
33
+ end
34
+
35
+ def finders
36
+ @finders ||= Finders::WpVersionFinders.new
34
37
  end
35
38
  end
36
39
  end
data/app/models/plugin.rb CHANGED
@@ -2,15 +2,15 @@ module WPScan
2
2
  # WordPress Plugin
3
3
  class Plugin < WpItem
4
4
  # See WpItem
5
- def initialize(name, target, opts = {})
6
- super(name, target, opts)
5
+ def initialize(slug, blog, opts = {})
6
+ super(slug, blog, opts)
7
7
 
8
- @uri = Addressable::URI.parse(target.url("wp-content/plugins/#{name}/"))
8
+ @uri = Addressable::URI.parse(blog.url("wp-content/plugins/#{slug}/"))
9
9
  end
10
10
 
11
11
  # @return [ JSON ]
12
12
  def db_data
13
- DB::Plugin.db_data(name)
13
+ DB::Plugin.db_data(slug)
14
14
  end
15
15
 
16
16
  # @param [ Hash ] opts
data/app/models/theme.rb CHANGED
@@ -5,10 +5,10 @@ module WPScan
5
5
  :license, :license_uri, :tags, :text_domain
6
6
 
7
7
  # See WpItem
8
- def initialize(name, target, opts = {})
9
- super(name, target, opts)
8
+ def initialize(slug, blog, opts = {})
9
+ super(slug, blog, opts)
10
10
 
11
- @uri = Addressable::URI.parse(target.url("wp-content/themes/#{name}/"))
11
+ @uri = Addressable::URI.parse(blog.url("wp-content/themes/#{slug}/"))
12
12
  @style_url = opts[:style_url] || url('style.css')
13
13
 
14
14
  parse_style
@@ -16,7 +16,7 @@ module WPScan
16
16
 
17
17
  # @return [ JSON ]
18
18
  def db_data
19
- DB::Theme.db_data(name)
19
+ DB::Theme.db_data(slug)
20
20
  end
21
21
 
22
22
  # @param [ Hash ] opts
@@ -39,7 +39,7 @@ module WPScan
39
39
  confidence: 100
40
40
  ).merge(version_detection: version_detection_opts)
41
41
 
42
- self.class.new(template, target, opts)
42
+ self.class.new(template, blog, opts)
43
43
  end
44
44
 
45
45
  # @param [ Integer ] depth
@@ -89,7 +89,7 @@ module WPScan
89
89
  def parse_style_tag(body, tag)
90
90
  value = body[/^\s*#{Regexp.escape(tag)}:[\t ]*([^\r\n]+)/i, 1]
91
91
 
92
- value && !value.strip.empty? ? value.strip : nil # rubocop:disable Style/SafeNavigation
92
+ value && !value.strip.empty? ? value.strip : nil
93
93
  end
94
94
 
95
95
  def ==(other)
@@ -18,9 +18,7 @@ module WPScan
18
18
  #
19
19
  # @return [ WPScan::Version, false ]
20
20
  def version(opts = {})
21
- if @version.nil?
22
- @version = Finders::TimthumbVersion::Base.find(self, version_detection_opts.merge(opts))
23
- end
21
+ @version = Finders::TimthumbVersion::Base.find(self, version_detection_opts.merge(opts)) if @version.nil?
24
22
 
25
23
  @version
26
24
  end
@@ -9,20 +9,22 @@ module WPScan
9
9
  READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
10
10
  CHANGELOGS = %w[changelog.txt CHANGELOG.md changelog.md].freeze
11
11
 
12
- attr_reader :uri, :name, :detection_opts, :version_detection_opts, :target, :db_data
12
+ attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :db_data
13
13
 
14
- # @param [ String ] name The plugin/theme name
15
- # @param [ Target ] target The targeted blog
14
+ delegate :homepage_res, :xpath_pattern_from_page, :in_scope_urls, to: :blog
15
+
16
+ # @param [ String ] slug The plugin/theme slug
17
+ # @param [ Target ] blog The targeted blog
16
18
  # @param [ Hash ] opts
17
19
  # @option opts [ Symbol ] :mode The detection mode to use
18
20
  # @option opts [ Hash ] :version_detection The options to use when looking for the version
19
21
  # @option opts [ String ] :url The URL of the item
20
- def initialize(name, target, opts = {})
21
- @name = URI.decode(name)
22
- @target = target
23
- @uri = Addressable::URI.parse(opts[:url]) if opts[:url]
22
+ def initialize(slug, blog, opts = {})
23
+ @slug = URI.decode(slug)
24
+ @blog = blog
25
+ @uri = Addressable::URI.parse(opts[:url]) if opts[:url]
24
26
 
25
- @detection_opts = { mode: opts[:mode] }
27
+ @detection_opts = { mode: opts[:mode] }
26
28
  @version_detection_opts = opts[:version_detection] || {}
27
29
 
28
30
  parse_finding_options(opts)
@@ -95,18 +97,16 @@ module WPScan
95
97
 
96
98
  # @return [ Boolean ]
97
99
  def ==(other)
98
- return false unless self.class == other.class
99
-
100
- name == other.name
100
+ self.class == other.class && slug == other.slug
101
101
  end
102
102
 
103
103
  def to_s
104
- name
104
+ slug
105
105
  end
106
106
 
107
- # @return [ Symbol ] The Class name associated to the item name
108
- def classify_name
109
- name.to_s.tr('-', '_').camelize.to_s.to_sym
107
+ # @return [ Symbol ] The Class symbol associated to the item
108
+ def classify
109
+ @classify ||= classify_slug(slug)
110
110
  end
111
111
 
112
112
  # @return [ String ] The readme url if found
@@ -2,7 +2,7 @@
2
2
  <% unless @plugins.empty? -%>
3
3
  <% last_index = @plugins.size - 1 -%>
4
4
  <% @plugins.each_with_index do |plugin, index| -%>
5
- <%= plugin.name.to_json %>: {
5
+ <%= plugin.slug.to_json %>: {
6
6
  <%= render('@wp_item', wp_item: plugin) %>,
7
7
  <%= render('@finding', item: plugin) -%>,
8
8
  <% if plugin.version -%>
@@ -2,7 +2,7 @@
2
2
  <% unless @themes.empty? -%>
3
3
  <% last_index = @themes.size - 1 -%>
4
4
  <% @themes.each_with_index do |theme, index| -%>
5
- <%= theme.name.to_json %>: {
5
+ <%= theme.slug.to_json %>: {
6
6
  <%= render('@theme', theme: theme) -%>
7
7
  }<% unless index == last_index -%>,<% end -%>
8
8
  <% end -%>
@@ -1,4 +1,4 @@
1
- "name": <%= @wp_item.name.to_json %>,
1
+ "slug": <%= @wp_item.slug.to_json %>,
2
2
  "location": <%= @wp_item.url.to_json %>,
3
3
  "latest_version": <%= @wp_item.latest_version ? @wp_item.latest_version.number.to_json : nil.to_json %>,
4
4
  "last_updated": <%= @wp_item.last_updated.to_json %>,
data/bin/wpscan CHANGED
@@ -9,7 +9,8 @@ WPScan::Scan.new do |s|
9
9
  WPScan::Controller::WpVersion.new <<
10
10
  WPScan::Controller::MainTheme.new <<
11
11
  WPScan::Controller::Enumeration.new <<
12
- WPScan::Controller::BruteForce.new
12
+ WPScan::Controller::BruteForce.new <<
13
+ WPScan::Controller::Aliases.new
13
14
 
14
15
  s.run
15
16
  end
data/lib/wpscan/db.rb CHANGED
@@ -1,10 +1,14 @@
1
- require 'wpscan/db/wp_item'
2
- require 'wpscan/db/updater'
3
- require 'wpscan/db/wp_items'
4
- require 'wpscan/db/plugins'
5
- require 'wpscan/db/themes'
6
- require 'wpscan/db/plugin'
7
- require 'wpscan/db/theme'
8
- require 'wpscan/db/wp_version'
9
- require 'wpscan/db/fingerprints'
10
- require 'wpscan/db/dynamic_finders'
1
+ require_relative 'db/wp_item'
2
+ require_relative 'db/updater'
3
+ require_relative 'db/wp_items'
4
+ require_relative 'db/plugins'
5
+ require_relative 'db/themes'
6
+ require_relative 'db/plugin'
7
+ require_relative 'db/theme'
8
+ require_relative 'db/wp_version'
9
+ require_relative 'db/fingerprints'
10
+
11
+ require_relative 'db/dynamic_finders/base'
12
+ require_relative 'db/dynamic_finders/plugin'
13
+ require_relative 'db/dynamic_finders/theme'
14
+ require_relative 'db/dynamic_finders/wordpress'
@@ -0,0 +1,41 @@
1
+ module WPScan
2
+ module DB
3
+ module DynamicFinders
4
+ class Base
5
+ # @return [ String ]
6
+ def self.db_file
7
+ @db_file ||= File.join(DB_DIR, 'dynamic_finders.yml')
8
+ end
9
+
10
+ # @return [ Hash ]
11
+ def self.db_data
12
+ # true allows aliases to be loaded
13
+ @db_data ||= YAML.safe_load(File.read(db_file), [Regexp], [], true)
14
+ end
15
+
16
+ # @return [ Array<Symbol> ]
17
+ def self.allowed_classes
18
+ @allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser]
19
+ end
20
+
21
+ # @param [ Symbol ] sym
22
+ def self.method_missing(sym)
23
+ super unless sym =~ /\A(passive|aggressive)_(.*)_finder_configs\z/i
24
+
25
+ finder_class = Regexp.last_match[2].camelize.to_sym
26
+
27
+ raise "#{finder_class} is not allowed as a Dynamic Finder" unless allowed_classes.include?(finder_class)
28
+
29
+ finder_configs(
30
+ finder_class,
31
+ Regexp.last_match[1] == 'aggressive'
32
+ )
33
+ end
34
+
35
+ def self.respond_to_missing?(sym, *_args)
36
+ sym =~ /\A(passive|aggressive)_(.*)_finder_configs\z/i
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end