diff --git a/app/finders/main_theme.rb b/app/finders/main_theme.rb index d353d451..377e8287 100644 --- a/app/finders/main_theme.rb +++ b/app/finders/main_theme.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true -require_relative 'main_theme/css_style' +require_relative 'main_theme/css_style_in_homepage' +require_relative 'main_theme/css_style_in_404_page' require_relative 'main_theme/woo_framework_meta_generator' require_relative 'main_theme/urls_in_homepage' +require_relative 'main_theme/urls_in_404_page' module WPScan module Finders @@ -14,9 +16,11 @@ module WPScan # @param [ WPScan::Target ] target def initialize(target) finders << - MainTheme::CssStyle.new(target) << + MainTheme::CssStyleInHomepage.new(target) << + MainTheme::CssStyleIn404Page.new(target) << MainTheme::WooFrameworkMetaGenerator.new(target) << - MainTheme::UrlsInHomepage.new(target) + MainTheme::UrlsInHomepage.new(target) << + MainTheme::UrlsIn404Page.new(target) end end end diff --git a/app/finders/main_theme/css_style_in_404_page.rb b/app/finders/main_theme/css_style_in_404_page.rb new file mode 100644 index 00000000..bbba5036 --- /dev/null +++ b/app/finders/main_theme/css_style_in_404_page.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module WPScan + module Finders + module MainTheme + # From the CSS style in the 404 page + class CssStyleIn404Page < CssStyleInHomepage + def passive(opts = {}) + passive_from_css_href(target.error_404_res, opts) || passive_from_style_code(target.error_404_res, opts) + end + end + end + end +end diff --git a/app/finders/main_theme/css_style.rb b/app/finders/main_theme/css_style_in_homepage.rb similarity index 85% rename from app/finders/main_theme/css_style.rb rename to app/finders/main_theme/css_style_in_homepage.rb index 4b978311..34bb8619 100644 --- a/app/finders/main_theme/css_style.rb +++ b/app/finders/main_theme/css_style_in_homepage.rb @@ -3,9 +3,9 @@ module WPScan module Finders module MainTheme - # From the css style - class CssStyle < CMSScanner::Finders::Finder - include Finders::WpItems::URLsInHomepage + # From the CSS style in the homepage + class CssStyleInHomepage < CMSScanner::Finders::Finder + include Finders::WpItems::UrlsInPage # To have the item_code_pattern method available here def create_theme(slug, style_url, opts) Model::Theme.new( diff --git a/app/finders/main_theme/urls_in_404_page.rb b/app/finders/main_theme/urls_in_404_page.rb new file mode 100644 index 00000000..55ffd407 --- /dev/null +++ b/app/finders/main_theme/urls_in_404_page.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module WPScan + module Finders + module MainTheme + # URLs In 404 Page Finder + class UrlsIn404Page < UrlsInHomepage + # @return [ Typhoeus::Response ] + def page_res + @page_res ||= target.error_404_res + end + end + end + end +end diff --git a/app/finders/main_theme/urls_in_homepage.rb b/app/finders/main_theme/urls_in_homepage.rb index b424d2ff..49c31004 100644 --- a/app/finders/main_theme/urls_in_homepage.rb +++ b/app/finders/main_theme/urls_in_homepage.rb @@ -5,7 +5,7 @@ module WPScan module MainTheme # URLs In Homepage Finder class UrlsInHomepage < CMSScanner::Finders::Finder - include WpItems::URLsInHomepage + include WpItems::UrlsInPage # @param [ Hash ] opts # @@ -21,6 +21,11 @@ module WPScan found end + + # @return [ Typhoeus::Response ] + def page_res + @page_res ||= target.homepage_res + end end end end diff --git a/app/finders/main_theme/woo_framework_meta_generator.rb b/app/finders/main_theme/woo_framework_meta_generator.rb index 427dc83c..03d9e68c 100644 --- a/app/finders/main_theme/woo_framework_meta_generator.rb +++ b/app/finders/main_theme/woo_framework_meta_generator.rb @@ -10,7 +10,7 @@ module WPScan PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze def passive(opts = {}) - return unless target.homepage_res.body =~ PATTERN + return unless target.homepage_res.body =~ PATTERN || target.error_404_res.body =~ PATTERN Model::Theme.new( Regexp.last_match[1], diff --git a/app/finders/plugins.rb b/app/finders/plugins.rb index 2efc8e74..926fd746 100644 --- a/app/finders/plugins.rb +++ b/app/finders/plugins.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative 'plugins/urls_in_homepage' +require_relative 'plugins/urls_in_404_page' require_relative 'plugins/known_locations' # From the DynamicFinders require_relative 'plugins/comment' @@ -22,6 +23,7 @@ module WPScan def initialize(target) finders << Plugins::UrlsInHomepage.new(target) << + Plugins::UrlsIn404Page.new(target) << Plugins::HeaderPattern.new(target) << Plugins::Comment.new(target) << Plugins::Xpath.new(target) << diff --git a/app/finders/plugins/urls_in_404_page.rb b/app/finders/plugins/urls_in_404_page.rb new file mode 100644 index 00000000..b5422900 --- /dev/null +++ b/app/finders/plugins/urls_in_404_page.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module WPScan + module Finders + module Plugins + # URLs In 404 Page Finder + # Typically, the items detected from URLs like /wp-content/plugins// + class UrlsIn404Page < UrlsInHomepage + # @return [ Typhoeus::Response ] + def page_res + @page_res ||= target.error_404_res + end + end + end + end +end diff --git a/app/finders/plugins/urls_in_homepage.rb b/app/finders/plugins/urls_in_homepage.rb index 22334f9f..832005aa 100644 --- a/app/finders/plugins/urls_in_homepage.rb +++ b/app/finders/plugins/urls_in_homepage.rb @@ -4,10 +4,9 @@ module WPScan module Finders module Plugins # URLs In Homepage Finder - # Typically, the items detected from URLs like - # /wp-content/plugins// + # Typically, the items detected from URLs like /wp-content/plugins// class UrlsInHomepage < CMSScanner::Finders::Finder - include WpItems::URLsInHomepage + include WpItems::UrlsInPage # @param [ Hash ] opts # @@ -21,6 +20,11 @@ module WPScan found end + + # @return [ Typhoeus::Response ] + def page_res + @page_res ||= target.homepage_res + end end end end diff --git a/app/finders/themes.rb b/app/finders/themes.rb index 95cedffe..250f9dfd 100644 --- a/app/finders/themes.rb +++ b/app/finders/themes.rb @@ -1,12 +1,13 @@ # frozen_string_literal: true require_relative 'themes/urls_in_homepage' +require_relative 'themes/urls_in_404_page' require_relative 'themes/known_locations' module WPScan module Finders module Themes - # themes Finder + # Themes Finder class Base include CMSScanner::Finders::SameTypeFinder @@ -14,6 +15,7 @@ module WPScan def initialize(target) finders << Themes::UrlsInHomepage.new(target) << + Themes::UrlsIn404Page.new(target) << Themes::KnownLocations.new(target) end end diff --git a/app/finders/themes/urls_in_404_page.rb b/app/finders/themes/urls_in_404_page.rb new file mode 100644 index 00000000..fd393232 --- /dev/null +++ b/app/finders/themes/urls_in_404_page.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module WPScan + module Finders + module Themes + # URLs In 04 Page Finder + class UrlsIn404Page < UrlsInHomepage + # @return [ Typhoeus::Response ] + def page_res + @page_res ||= target.error_404_res + end + end + end + end +end diff --git a/app/finders/themes/urls_in_homepage.rb b/app/finders/themes/urls_in_homepage.rb index 4709bba0..c8071355 100644 --- a/app/finders/themes/urls_in_homepage.rb +++ b/app/finders/themes/urls_in_homepage.rb @@ -5,7 +5,7 @@ module WPScan module Themes # URLs In Homepage Finder class UrlsInHomepage < CMSScanner::Finders::Finder - include WpItems::URLsInHomepage + include WpItems::UrlsInPage # @param [ Hash ] opts # @@ -19,6 +19,11 @@ module WPScan found end + + # @return [ Typhoeus::Response ] + def page_res + @page_res ||= target.homepage_res + end end end end diff --git a/app/finders/wp_items.rb b/app/finders/wp_items.rb index ded84746..e9f1dd4b 100644 --- a/app/finders/wp_items.rb +++ b/app/finders/wp_items.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -require_relative 'wp_items/urls_in_homepage' +require_relative 'wp_items/urls_in_page' diff --git a/app/finders/wp_items/urls_in_homepage.rb b/app/finders/wp_items/urls_in_page.rb similarity index 92% rename from app/finders/wp_items/urls_in_homepage.rb rename to app/finders/wp_items/urls_in_page.rb index d17ffa44..200608c6 100644 --- a/app/finders/wp_items/urls_in_homepage.rb +++ b/app/finders/wp_items/urls_in_page.rb @@ -4,7 +4,7 @@ module WPScan module Finders module WpItems # URLs In Homepage Module to use in plugins & themes finders - module URLsInHomepage + module UrlsInPage # @param [ String ] type plugins / themes # @param [ Boolean ] uniq Wether or not to apply the #uniq on the results # @@ -12,7 +12,7 @@ module WPScan def items_from_links(type, uniq = true) found = [] - target.in_scope_uris(target.homepage_res) do |uri| + target.in_scope_uris(page_res) do |uri| next unless uri.to_s =~ item_attribute_pattern(type) slug = Regexp.last_match[1]&.strip @@ -30,7 +30,7 @@ module WPScan def items_from_codes(type, uniq = true) found = [] - target.homepage_res.html.xpath('//script[not(@src)]|//style[not(@src)]').each do |tag| + page_res.html.xpath('//script[not(@src)]|//style[not(@src)]').each do |tag| code = tag.text.to_s next if code.empty? diff --git a/app/models/wp_item.rb b/app/models/wp_item.rb index b43b6a59..e001946b 100644 --- a/app/models/wp_item.rb +++ b/app/models/wp_item.rb @@ -14,7 +14,7 @@ module WPScan attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data - delegate :homepage_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog + delegate :homepage_res, :error_404_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog # @param [ String ] slug The plugin/theme slug # @param [ Target ] blog The targeted blog diff --git a/lib/wpscan/finders/dynamic_finder/finder.rb b/lib/wpscan/finders/dynamic_finder/finder.rb index 60c42f94..b4349588 100644 --- a/lib/wpscan/finders/dynamic_finder/finder.rb +++ b/lib/wpscan/finders/dynamic_finder/finder.rb @@ -44,19 +44,27 @@ module WPScan # # @param [ Typhoeus::Response ] response # @param [ Hash ] opts - # @return [ Mixed ] + # @return [ Mixed: nil, Object, Array ] def find(_response, _opts = {}) raise NoMethodError end # @param [ Hash ] opts + # @return [ Mixed ] See #find def passive(opts = {}) return if self.class::PATH - find(target.homepage_res, opts) + homepage_result = find(target.homepage_res, opts) + + if homepage_result + return homepage_result unless homepage_result.is_a?(Array) && homepage_result.empty? + end + + find(target.error_404_res, opts) end # @param [ Hash ] opts + # @return [ Mixed ] See #find def aggressive(opts = {}) return unless self.class::PATH diff --git a/lib/wpscan/finders/dynamic_finder/wp_items/finder.rb b/lib/wpscan/finders/dynamic_finder/wp_items/finder.rb index bd348550..ee460103 100644 --- a/lib/wpscan/finders/dynamic_finder/wp_items/finder.rb +++ b/lib/wpscan/finders/dynamic_finder/wp_items/finder.rb @@ -31,9 +31,11 @@ module WPScan passive_configs.each do |slug, configs| configs.each do |klass, config| - item = process_response(opts, target.homepage_res, slug, klass, config) + [target.homepage_res, target.error_404_res].each do |page_res| + item = process_response(opts, page_res, slug, klass, config) - found << item if item.is_a?(Model::WpItem) + found << item if item.is_a?(Model::WpItem) + end end end diff --git a/lib/wpscan/target/platform/wordpress.rb b/lib/wpscan/target/platform/wordpress.rb index a3360562..dbb41bc9 100644 --- a/lib/wpscan/target/platform/wordpress.rb +++ b/lib/wpscan/target/platform/wordpress.rb @@ -24,21 +24,10 @@ module WPScan # @param [ Symbol ] detection_mode # - # @return [ Boolean ] - # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity + # @return [ Boolean ] Whether or not the target is running WordPress def wordpress?(detection_mode) - in_scope_uris(homepage_res) do |uri| - return true if WORDPRESS_PATTERN.match?(uri.path) || WP_JSON_OEMBED_PATTERN.match?(uri.path) - end - - return true if homepage_res.html.css('meta[name="generator"]').any? do |node| - /wordpress/i.match?(node['content']) - end - - return true unless comments_from_page(/wordpress/i, homepage_res).empty? - - return true if homepage_res.html.xpath('//script[not(@src)]').any? do |node| - WP_ADMIN_AJAX_PATTERN.match?(node.text) + [homepage_res, error_404_res].each do |page_res| + return true if wordpress_from_meta_comments_or_scripts?(page_res) end if %i[mixed aggressive].include?(detection_mode) @@ -51,7 +40,26 @@ module WPScan false end - # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity + + # @param [ Typhoeus::Response ] response + # @return [ Boolean ] + def wordpress_from_meta_comments_or_scripts?(response) + in_scope_uris(response) do |uri| + return true if WORDPRESS_PATTERN.match?(uri.path) || WP_JSON_OEMBED_PATTERN.match?(uri.path) + end + + return true if response.html.css('meta[name="generator"]').any? do |node| + /wordpress/i.match?(node['content']) + end + + return true unless comments_from_page(/wordpress/i, response).empty? + + return true if response.html.xpath('//script[not(@src)]').any? do |node| + WP_ADMIN_AJAX_PATTERN.match?(node.text) + end + + false + end COOKIE_PATTERNS = { 'vjs' => /createCookie\('vjs','(?\d+)',\d+\);/i diff --git a/lib/wpscan/target/platform/wordpress/custom_directories.rb b/lib/wpscan/target/platform/wordpress/custom_directories.rb index 1b21e557..3ca40412 100644 --- a/lib/wpscan/target/platform/wordpress/custom_directories.rb +++ b/lib/wpscan/target/platform/wordpress/custom_directories.rb @@ -19,13 +19,15 @@ module WPScan # scope_url_pattern is from CMSScanner::Target pattern = %r{#{scope_url_pattern}([\w\s\-/]+?)\\?/(?:themes|plugins|uploads|cache)\\?/}i - in_scope_uris(homepage_res, '//link/@href|//script/@src|//img/@src') do |uri| - return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern) - end + [homepage_res, error_404_res].each do |page_res| + in_scope_uris(page_res, '//link/@href|//script/@src|//img/@src') do |uri| + return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern) + end - # Checks for the pattern in raw JS code, as well as @content attributes of meta tags - xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, homepage_res) do |match| - return @content_dir = match[1] + # Checks for the pattern in raw JS code, as well as @content attributes of meta tags + xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, page_res) do |match| + return @content_dir = match[1] + end end return @content_dir = 'wp-content' if default_content_dir_exists? @@ -104,8 +106,10 @@ module WPScan # url_pattern is from CMSScanner::Target pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i - in_scope_uris(homepage_res) do |uri| - return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern) + [homepage_res, error_404_res].each do |page_res| + in_scope_uris(page_res) do |uri| + return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern) + end end @sub_dir = false diff --git a/spec/app/finders/main_theme/css_style_in_404_page_spec.rb b/spec/app/finders/main_theme/css_style_in_404_page_spec.rb new file mode 100644 index 00000000..ce778ed0 --- /dev/null +++ b/spec/app/finders/main_theme/css_style_in_404_page_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +describe WPScan::Finders::MainTheme::CssStyleIn404Page do + subject(:finder) { described_class.new(target) } + let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) } + let(:url) { 'http://wp.lab/' } + let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style_in_404_page') } + + # This stuff is just a child class of CssStyleInHomepage (using the error_404_res rather than homepage_res) + # which already has a spec +end diff --git a/spec/app/finders/main_theme/css_style_spec.rb b/spec/app/finders/main_theme/css_style_in_homepage_spec.rb similarity index 87% rename from spec/app/finders/main_theme/css_style_spec.rb rename to spec/app/finders/main_theme/css_style_in_homepage_spec.rb index 1cf30fb8..c067bb30 100644 --- a/spec/app/finders/main_theme/css_style_spec.rb +++ b/spec/app/finders/main_theme/css_style_in_homepage_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -describe WPScan::Finders::MainTheme::CssStyle do +describe WPScan::Finders::MainTheme::CssStyleInHomepage do subject(:finder) { described_class.new(target) } let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) } let(:url) { 'http://wp.lab/' } - let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style') } + let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style_in_homepage') } describe '#passive' do after do @@ -33,7 +33,7 @@ describe WPScan::Finders::MainTheme::CssStyle do @expected = WPScan::Model::Theme.new( 'twentyfifteen', target, - found_by: 'Css Style (Passive Detection)', + found_by: 'Css Style In Homepage (Passive Detection)', confidence: 70, style_url: 'http://wp.lab/wp-content/themes/twentyfifteen/style.css?ver=4.1.1' ) @@ -47,7 +47,7 @@ describe WPScan::Finders::MainTheme::CssStyle do @expected = WPScan::Model::Theme.new( 'custom', target, - found_by: 'Css Style (Passive Detection)', + found_by: 'Css Style In Homepage (Passive Detection)', confidence: 70, style_url: 'http://wp.lab/wp-content/themes/custom/style.css' ) diff --git a/spec/app/finders/main_theme/urls_in_404_page_spec.rb.rb b/spec/app/finders/main_theme/urls_in_404_page_spec.rb.rb new file mode 100644 index 00000000..01cf483d --- /dev/null +++ b/spec/app/finders/main_theme/urls_in_404_page_spec.rb.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +describe WPScan::Finders::MainTheme::UrlsIn404Page do + subject(:finder) { described_class.new(target) } + let(:target) { WPScan::Target.new(url) } + let(:url) { 'http://wp.lab/' } + let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'urls_in_404_page') } + + # This stuff is just a child class of URLsInHomepage (using the error_404_res rather than homepage_res) + # which already has a spec +end diff --git a/spec/app/finders/main_theme/urls_in_homepage_spec.rb b/spec/app/finders/main_theme/urls_in_homepage_spec.rb index 9f7565f9..314b63fb 100644 --- a/spec/app/finders/main_theme/urls_in_homepage_spec.rb +++ b/spec/app/finders/main_theme/urls_in_homepage_spec.rb @@ -6,7 +6,8 @@ describe WPScan::Finders::MainTheme::UrlsInHomepage do let(:url) { 'http://wp.lab/' } let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'urls_in_homepage') } - it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do + it_behaves_like 'App::Finders::WpItems::UrlsInPage' do + let(:page_url) { url } let(:type) { 'themes' } let(:uniq_links) { false } let(:uniq_codes) { false } @@ -18,6 +19,8 @@ describe WPScan::Finders::MainTheme::UrlsInHomepage do before do stub_request(:get, /.*.css/) stub_request(:get, target.url).to_return(body: File.read(fixtures.join('found.html'))) + + allow(target).to receive(:content_dir).and_return('wp-content') end it 'returns the expected Themes' do diff --git a/spec/app/finders/main_theme/woo_framework_meta_generator_spec.rb b/spec/app/finders/main_theme/woo_framework_meta_generator_spec.rb index e73d16b8..fc7f4f10 100644 --- a/spec/app/finders/main_theme/woo_framework_meta_generator_spec.rb +++ b/spec/app/finders/main_theme/woo_framework_meta_generator_spec.rb @@ -7,32 +7,50 @@ describe WPScan::Finders::MainTheme::WooFrameworkMetaGenerator do let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'woo_framework_meta_generator') } describe '#passive' do - after do - stub_request(:get, url).to_return(body: File.read(fixtures.join(@file))) - - expect(finder.passive).to eql @expected + before do + stub_request(:get, url).to_return(body: File.read(fixtures.join(homepage_fixture))) + stub_request(:get, ERROR_404_URL_PATTERN).to_return(body: File.read(fixtures.join(error_404_fixture))) end context 'when no Woo generator' do + let(:homepage_fixture) { 'no_woo_generator.html' } + let(:error_404_fixture) { 'no_woo_generator.html' } + it 'returns nil' do - @file = 'no_woo_generator.html' - @expected = nil + expect(finder.passive).to eql nil end end context 'when Woo generator' do before do - expect(target).to receive(:content_dir).at_least(1).and_return('wp-content') + allow(target).to receive(:content_dir).and_return('wp-content') stub_request(:get, "#{url}wp-content/themes/Merchant/style.css") end - it 'returns the expected theme' do - @file = 'woo_generator.html' - @expected = WPScan::Model::Theme.new( - 'Merchant', target, - found_by: 'Woo Framework Meta Generator (Passive Detection)', - confidence: 80 - ) + context 'from the homepage' do + let(:homepage_fixture) { 'woo_generator.html' } + let(:error_404_fixture) { 'no_woo_generator.html' } + + it 'returns the expected theme' do + expect(finder.passive).to eql WPScan::Model::Theme.new( + 'Merchant', target, + found_by: 'Woo Framework Meta Generator (Passive Detection)', + confidence: 80 + ) + end + end + + context 'from the 404 page' do + let(:homepage_fixture) { 'no_woo_generator.html' } + let(:error_404_fixture) { 'woo_generator.html' } + + it 'returns the expected theme' do + expect(finder.passive).to eql WPScan::Model::Theme.new( + 'Merchant', target, + found_by: 'Woo Framework Meta Generator (Passive Detection)', + confidence: 80 + ) + end end end end diff --git a/spec/app/finders/main_theme_spec.rb b/spec/app/finders/main_theme_spec.rb index 11037430..a047c50b 100644 --- a/spec/app/finders/main_theme_spec.rb +++ b/spec/app/finders/main_theme_spec.rb @@ -8,7 +8,7 @@ describe WPScan::Finders::MainTheme::Base do describe '#finders' do it 'contains the expected finders' do expect(main_theme.finders.map { |f| f.class.to_s.demodulize }) - .to eq %w[CssStyle WooFrameworkMetaGenerator UrlsInHomepage] + .to eq %w[CssStyleInHomepage CssStyleIn404Page WooFrameworkMetaGenerator UrlsInHomepage UrlsIn404Page] end end end diff --git a/spec/app/finders/plugins/urls_in_404_page_spec.rb b/spec/app/finders/plugins/urls_in_404_page_spec.rb new file mode 100644 index 00000000..f698c53c --- /dev/null +++ b/spec/app/finders/plugins/urls_in_404_page_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +describe WPScan::Finders::Plugins::UrlsIn404Page do + subject(:finder) { described_class.new(target) } + let(:target) { WPScan::Target.new(url) } + let(:url) { 'https://wp.lab/' } + let(:fixtures) { FINDERS_FIXTURES.join('plugins', 'urls_in_404_page') } + + # This stuff is just a child class of URLsInHomepage (using the error_404_res rather than homepage_res) + # which already has a spec +end diff --git a/spec/app/finders/plugins/urls_in_homepage_spec.rb b/spec/app/finders/plugins/urls_in_homepage_spec.rb index 225eb12f..5087733a 100644 --- a/spec/app/finders/plugins/urls_in_homepage_spec.rb +++ b/spec/app/finders/plugins/urls_in_homepage_spec.rb @@ -8,7 +8,8 @@ describe WPScan::Finders::Plugins::UrlsInHomepage do before { target.scope << 'sub.lab' } - it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do + it_behaves_like 'App::Finders::WpItems::UrlsInPage' do + let(:page_url) { url } let(:type) { 'plugins' } let(:uniq_links) { true } let(:uniq_codes) { true } diff --git a/spec/app/finders/plugins_spec.rb b/spec/app/finders/plugins_spec.rb index fea6b1b5..6b93e61a 100644 --- a/spec/app/finders/plugins_spec.rb +++ b/spec/app/finders/plugins_spec.rb @@ -8,7 +8,7 @@ describe WPScan::Finders::Plugins::Base do describe '#finders' do it 'contains the expected finders' do expect(plugins.finders.map { |f| f.class.to_s.demodulize }) - .to eq %w[UrlsInHomepage HeaderPattern Comment Xpath BodyPattern JavascriptVar KnownLocations] + .to eq %w[UrlsInHomepage UrlsIn404Page HeaderPattern Comment Xpath BodyPattern JavascriptVar KnownLocations] end end end diff --git a/spec/app/finders/themes/urls_in_404_page_spec.rb b/spec/app/finders/themes/urls_in_404_page_spec.rb new file mode 100644 index 00000000..fc723894 --- /dev/null +++ b/spec/app/finders/themes/urls_in_404_page_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +describe WPScan::Finders::Themes::UrlsIn404Page do + subject(:finder) { described_class.new(target) } + let(:target) { WPScan::Target.new(url) } + let(:url) { 'http://wp.lab/' } + let(:fixtures) { FINDERS_FIXTURES.join('themes', 'urls_in_404_page') } + + # This stuff is just a child class of URLsInHomepage (using the error_404_res rather than homepage_res) + # which already has a spec +end diff --git a/spec/app/finders/themes/urls_in_homepage_spec.rb b/spec/app/finders/themes/urls_in_homepage_spec.rb index d6b152bb..6f5aa052 100644 --- a/spec/app/finders/themes/urls_in_homepage_spec.rb +++ b/spec/app/finders/themes/urls_in_homepage_spec.rb @@ -8,7 +8,8 @@ describe WPScan::Finders::Themes::UrlsInHomepage do # before { target.scope << 'sub.lab' } - it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do + it_behaves_like 'App::Finders::WpItems::UrlsInPage' do + let(:page_url) { url } let(:type) { 'themes' } let(:uniq_links) { true } let(:uniq_codes) { true } diff --git a/spec/app/finders/themes_spec.rb b/spec/app/finders/themes_spec.rb index 7b033c57..a4f75e3e 100644 --- a/spec/app/finders/themes_spec.rb +++ b/spec/app/finders/themes_spec.rb @@ -8,7 +8,7 @@ describe WPScan::Finders::Themes::Base do describe '#finders' do it 'contains the expected finders' do expect(themes.finders.map { |f| f.class.to_s.demodulize }) - .to eq %w[UrlsInHomepage KnownLocations] + .to eq %w[UrlsInHomepage UrlsIn404Page KnownLocations] end end end diff --git a/spec/app/finders/users/wp_json_api_spec.rb b/spec/app/finders/users/wp_json_api_spec.rb index e24af419..a9af123e 100644 --- a/spec/app/finders/users/wp_json_api_spec.rb +++ b/spec/app/finders/users/wp_json_api_spec.rb @@ -87,6 +87,8 @@ describe WPScan::Finders::Users::WpJsonApi do describe '#api_url' do let(:fixtures) { super().join('api_url') } + before { allow(target).to receive(:sub_dir).and_return(false) } + context 'when url in the homepage' do { in_scope: 'https://wp.lab/wp-json/wp/v2/users/', @@ -100,7 +102,7 @@ describe WPScan::Finders::Users::WpJsonApi do end context 'when subdir' do - before { allow(target).to receive(:subdir).and_return('cms') } + before { allow(target).to receive(:sub_dir).and_return('cms') } { in_scope_subdir: 'https://wp.lab/cms/wp-json/wp/v2/users/', diff --git a/spec/fixtures/finders/main_theme/css_style/link_href.html b/spec/fixtures/finders/main_theme/css_style_in_homepage/link_href.html similarity index 100% rename from spec/fixtures/finders/main_theme/css_style/link_href.html rename to spec/fixtures/finders/main_theme/css_style_in_homepage/link_href.html diff --git a/spec/fixtures/finders/main_theme/css_style/no_in_scope_style.html b/spec/fixtures/finders/main_theme/css_style_in_homepage/no_in_scope_style.html similarity index 100% rename from spec/fixtures/finders/main_theme/css_style/no_in_scope_style.html rename to spec/fixtures/finders/main_theme/css_style_in_homepage/no_in_scope_style.html diff --git a/spec/fixtures/finders/main_theme/css_style/style_code.html b/spec/fixtures/finders/main_theme/css_style_in_homepage/style_code.html similarity index 100% rename from spec/fixtures/finders/main_theme/css_style/style_code.html rename to spec/fixtures/finders/main_theme/css_style_in_homepage/style_code.html diff --git a/spec/lib/finders/dynamic_finder/plugin_version_spec.rb b/spec/lib/finders/dynamic_finder/plugin_version_spec.rb index 8fa96ff8..04bce07c 100644 --- a/spec/lib/finders/dynamic_finder/plugin_version_spec.rb +++ b/spec/lib/finders/dynamic_finder/plugin_version_spec.rb @@ -38,13 +38,21 @@ WPScan::DB::DynamicFinders::Plugin.versions_finders_configs.each do |slug, confi end end - let(:stubbed_response) { { body: 'aa' } } + before { allow(target).to receive(:content_dir).and_return('wp-content') } describe '#passive', slow: true do before do - stub_request(:get, target.url).to_return(stubbed_response) + if defined?(stubbed_homepage_res) + stub_request(:get, target.url).to_return(stubbed_homepage_res) + else + stub_request(:get, target.url) + end - expect(target).to receive(:content_dir).at_least(1).and_return('wp-content') + if defined?(stubbed_404_res) + stub_request(:get, ERROR_404_URL_PATTERN).to_return(stubbed_404_res) + else + stub_request(:get, ERROR_404_URL_PATTERN) + end end if config['path'] @@ -56,27 +64,63 @@ WPScan::DB::DynamicFinders::Plugin.versions_finders_configs.each do |slug, confi else context 'when no PATH' do context 'when the version is detected' do - let(:stubbed_response) do - df_stubbed_response( - fixtures.join("#{finder_super_class.underscore}_passive_all.html"), - finder_super_class - ) + context 'from the homepage' do + let(:ie_url) { target.url } + let(:stubbed_homepage_res) do + df_stubbed_response( + fixtures.join("#{finder_super_class.underscore}_passive_all.html"), + finder_super_class + ) + end + + it 'returns the expected version/s' do + found = [*finder.passive] + + expect(found).to_not be_empty + + found.each_with_index do |version, index| + expected_version = expected.at(index) + expected_ie = expected_version['interesting_entries'].map do |ie| + ie.gsub(target.url + ',', ie_url + ',') + end + + expect(version).to be_a WPScan::Model::Version + expect(version.number).to eql expected_version['number'].to_s + expect(version.found_by).to eql expected_version['found_by'] + expect(version.interesting_entries).to match_array expected_ie + + expect(version.confidence).to eql expected_version['confidence'] if expected_version['confidence'] + end + end end - it 'returns the expected version/s from the homepage' do - found = [*finder.passive] + context 'from the 404' do + let(:ie_url) { target.error_404_url } + let(:stubbed_404_res) do + df_stubbed_response( + fixtures.join("#{finder_super_class.underscore}_passive_all.html"), + finder_super_class + ) + end - expect(found).to_not be_empty + it 'returns the expected version/s' do + found = [*finder.passive] - found.each_with_index do |version, index| - expected_version = expected.at(index) + expect(found).to_not be_empty - expect(version).to be_a WPScan::Model::Version - expect(version.number).to eql expected_version['number'].to_s - expect(version.found_by).to eql expected_version['found_by'] - expect(version.interesting_entries).to match_array expected_version['interesting_entries'] + found.each_with_index do |version, index| + expected_version = expected.at(index) + expected_ie = expected_version['interesting_entries'].map do |ie| + ie.gsub(target.url + ',', ie_url + ',') + end - expect(version.confidence).to eql expected_version['confidence'] if expected_version['confidence'] + expect(version).to be_a WPScan::Model::Version + expect(version.number).to eql expected_version['number'].to_s + expect(version.found_by).to eql expected_version['found_by'] + expect(version.interesting_entries).to match_array expected_ie + + expect(version.confidence).to eql expected_version['confidence'] if expected_version['confidence'] + end end end end @@ -91,11 +135,10 @@ WPScan::DB::DynamicFinders::Plugin.versions_finders_configs.each do |slug, confi end describe '#aggressive' do - let(:fixtures) { super().join(slug, finder_class.underscore) } + let(:fixtures) { super().join(slug, finder_class.underscore) } + let(:stubbed_response) { { body: 'aa' } } before do - expect(target).to receive(:content_dir).at_least(1).and_return('wp-content') - stub_request(:get, plugin.url(config['path'])).to_return(stubbed_response) if config['path'] end diff --git a/spec/lib/finders/dynamic_finder/theme_version_spec.rb b/spec/lib/finders/dynamic_finder/theme_version_spec.rb index 6ae926e5..4482c6a0 100644 --- a/spec/lib/finders/dynamic_finder/theme_version_spec.rb +++ b/spec/lib/finders/dynamic_finder/theme_version_spec.rb @@ -38,8 +38,6 @@ WPScan::DB::DynamicFinders::Theme.versions_finders_configs.each do |slug, config end end - let(:stubbed_response) { { body: 'aa' } } - before do allow(target).to receive(:content_dir).and_return('wp-content') @@ -48,7 +46,19 @@ WPScan::DB::DynamicFinders::Theme.versions_finders_configs.each do |slug, config end describe '#passive', slow: true do - before { stub_request(:get, target.url).to_return(stubbed_response) } + before do + if defined?(stubbed_homepage_res) + stub_request(:get, target.url).to_return(stubbed_homepage_res) + else + stub_request(:get, target.url) + end + + if defined?(stubbed_404_res) + stub_request(:get, ERROR_404_URL_PATTERN).to_return(stubbed_404_res) + else + stub_request(:get, ERROR_404_URL_PATTERN) + end + end if config['path'] context 'when PATH' do @@ -59,27 +69,63 @@ WPScan::DB::DynamicFinders::Theme.versions_finders_configs.each do |slug, config else context 'when no PATH' do context 'when the version is detected' do - let(:stubbed_response) do - df_stubbed_response( - fixtures.join("#{finder_super_class.underscore}_passive_all.html"), - finder_super_class - ) + context 'from the homepage' do + let(:ie_url) { target.url } + let(:stubbed_homepage_res) do + df_stubbed_response( + fixtures.join("#{finder_super_class.underscore}_passive_all.html"), + finder_super_class + ) + end + + it 'returns the expected version/s' do + found = [*finder.passive] + + expect(found).to_not be_empty + + found.each_with_index do |version, index| + expected_version = expected.at(index) + expected_ie = expected_version['interesting_entries'].map do |ie| + ie.gsub(target.url + ',', ie_url + ',') + end + + expect(version).to be_a WPScan::Model::Version + expect(version.number).to eql expected_version['number'].to_s + expect(version.found_by).to eql expected_version['found_by'] + expect(version.interesting_entries).to match_array expected_ie + + expect(version.confidence).to eql expected_version['confidence'] if expected_version['confidence'] + end + end end - it 'returns the expected version/s from the homepage' do - found = [*finder.passive] + context 'from the 404' do + let(:ie_url) { target.error_404_url } + let(:stubbed_404_res) do + df_stubbed_response( + fixtures.join("#{finder_super_class.underscore}_passive_all.html"), + finder_super_class + ) + end - expect(found).to_not be_empty + it 'returns the expected version/s' do + found = [*finder.passive] - found.each_with_index do |version, index| - expected_version = expected.at(index) + expect(found).to_not be_empty - expect(version).to be_a WPScan::Model::Version - expect(version.number).to eql expected_version['number'].to_s - expect(version.found_by).to eql expected_version['found_by'] - expect(version.interesting_entries).to match_array expected_version['interesting_entries'] + found.each_with_index do |version, index| + expected_version = expected.at(index) + expected_ie = expected_version['interesting_entries'].map do |ie| + ie.gsub(target.url + ',', ie_url + ',') + end - expect(version.confidence).to eql expected_version['confidence'] if expected_version['confidence'] + expect(version).to be_a WPScan::Model::Version + expect(version.number).to eql expected_version['number'].to_s + expect(version.found_by).to eql expected_version['found_by'] + expect(version.interesting_entries).to match_array expected_ie + + expect(version.confidence).to eql expected_version['confidence'] if expected_version['confidence'] + end end end end @@ -94,7 +140,8 @@ WPScan::DB::DynamicFinders::Theme.versions_finders_configs.each do |slug, config end describe '#aggressive' do - let(:fixtures) { super().join(slug, finder_class.underscore) } + let(:fixtures) { super().join(slug, finder_class.underscore) } + let(:stubbed_response) { { body: 'aa' } } before do stub_request(:get, theme.url(config['path'])).to_return(stubbed_response) if config['path'] diff --git a/spec/lib/finders/dynamic_finder/wp_version_spec.rb b/spec/lib/finders/dynamic_finder/wp_version_spec.rb index 1a7f5fd4..29161120 100644 --- a/spec/lib/finders/dynamic_finder/wp_version_spec.rb +++ b/spec/lib/finders/dynamic_finder/wp_version_spec.rb @@ -25,7 +25,10 @@ WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.each do |finder_c let(:stubbed_response) { { body: '' } } describe '#passive' do - before { stub_request(:get, target.url).to_return(stubbed_response) } + before do + stub_request(:get, target.url).to_return(stubbed_response) + stub_request(:get, ERROR_404_URL_PATTERN) + end if config['path'] context 'when PATH' do @@ -66,7 +69,7 @@ WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.each do |finder_c let(:fixtures) { super().join(finder_class.underscore) } before do - allow(target).to receive(:sub_dir).and_return(nil) + allow(target).to receive(:sub_dir).and_return(false) stub_request(:get, target.url(config['path'])).to_return(stubbed_response) if config['path'] end diff --git a/spec/shared_examples.rb b/spec/shared_examples.rb index 526acb76..3a88dfbc 100644 --- a/spec/shared_examples.rb +++ b/spec/shared_examples.rb @@ -5,6 +5,6 @@ require 'shared_examples/views/wp_version' require 'shared_examples/views/main_theme' require 'shared_examples/views/enumeration' require 'shared_examples/target/platform/wordpress' -require 'shared_examples/finders/wp_items/urls_in_homepage' +require 'shared_examples/finders/wp_items/urls_in_page' require 'shared_examples/references' require 'shared_examples/dynamic_finders/wp_items' diff --git a/spec/shared_examples/dynamic_finders/wp_items.rb b/spec/shared_examples/dynamic_finders/wp_items.rb index 8d998a56..c30c6b18 100644 --- a/spec/shared_examples/dynamic_finders/wp_items.rb +++ b/spec/shared_examples/dynamic_finders/wp_items.rb @@ -16,13 +16,15 @@ shared_examples WPScan::Finders::DynamicFinder::WpItems::Finder do describe '#passive' do before do - stub_request(:get, target.url).to_return(body: body) + stub_request(:get, target.url).to_return(body: homepage_body) + stub_request(:get, ERROR_404_URL_PATTERN).to_return(body: error_404_body) allow(target).to receive(:content_dir).and_return('wp-content') end context 'when no matches' do - let(:body) { '' } + let(:homepage_body) { '' } + let(:error_404_body) { '' } it 'returns an empty array' do expect(finder.passive).to eql([]) @@ -30,9 +32,7 @@ shared_examples WPScan::Finders::DynamicFinder::WpItems::Finder do end context 'when matches' do - let(:body) { File.read(passive_fixture) } - - it 'contains the expected items' do + let(:expected_items) do expected = [] finder.passive_configs.each do |slug, configs| @@ -48,7 +48,25 @@ shared_examples WPScan::Finders::DynamicFinder::WpItems::Finder do end end - expect(finder.passive).to match_array(expected.map { |item| eql(item) }) + expected + end + + context 'from the homepage' do + let(:homepage_body) { File.read(passive_fixture) } + let(:error_404_body) { '' } + + it 'contains the expected items' do + expect(finder.passive).to match_array(expected_items.map { |item| eql(item) }) + end + end + + context 'from the 404' do + let(:homepage_body) { '' } + let(:error_404_body) { File.read(passive_fixture) } + + it 'contains the expected items' do + expect(finder.passive).to match_array(expected_items.map { |item| eql(item) }) + end end end end diff --git a/spec/shared_examples/finders/wp_items/urls_in_homepage.rb b/spec/shared_examples/finders/wp_items/urls_in_page.rb similarity index 88% rename from spec/shared_examples/finders/wp_items/urls_in_homepage.rb rename to spec/shared_examples/finders/wp_items/urls_in_page.rb index d2b934c1..b3598a24 100644 --- a/spec/shared_examples/finders/wp_items/urls_in_homepage.rb +++ b/spec/shared_examples/finders/wp_items/urls_in_page.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -shared_examples 'App::Finders::WpItems::URLsInHomepage' do +shared_examples 'App::Finders::WpItems::UrlsInPage' do before do - stub_request(:get, finder.target.url).to_return(body: File.read(fixtures.join(file))) + stub_request(:get, page_url).to_return(body: File.read(fixtures.join(file))) end describe '#items_from_links' do diff --git a/spec/shared_examples/target/platform/wordpress.rb b/spec/shared_examples/target/platform/wordpress.rb index 3088ffae..32432996 100644 --- a/spec/shared_examples/target/platform/wordpress.rb +++ b/spec/shared_examples/target/platform/wordpress.rb @@ -7,14 +7,17 @@ shared_examples WPScan::Target::Platform::WordPress do let(:fixtures) { FIXTURES.join('target', 'platform', 'wordpress') } - describe '#wordpress?' do + describe '#wordpress?, wordpress_from_meta_comments_or_scripts?' do let(:fixtures) { super().join('detection') } before do stub_request(:get, target.url).to_return(body: File.read(fixtures.join("#{homepage}.html"))) + stub_request(:get, ERROR_404_URL_PATTERN).to_return(body: File.read(fixtures.join("#{page_404}.html"))) end context 'when pattern/s in the homepage' do + let(:page_404) { 'not_wp' } + %w[default wp_includes only_scripts meta_generator comments mu_plugins wp_admin wp_json_oembed].each do |file| context "when a wordpress page (#{file}.html)" do let(:homepage) { file } @@ -29,39 +32,55 @@ shared_examples WPScan::Target::Platform::WordPress do context 'when no clues in the homepage' do let(:homepage) { 'not_wp' } - context 'when only passive detection mode' do - it 'returns false' do - expect(subject.wordpress?(:passive)).to be false + context 'when pattern/s in the 404 page' do + %w[default wp_includes only_scripts meta_generator comments mu_plugins wp_admin wp_json_oembed].each do |file| + context "when a wordpress page (#{file}.html)" do + let(:page_404) { file } + + it 'returns true' do + expect(subject.wordpress?(:mixed)).to be true + end + end end end - context 'when mixed or aggressive detection modes' do - context 'when wp-admin/install.php and wp-login.php not there' do + context 'when no clues in the 404 page' do + let(:page_404) { 'not_wp' } + + context 'when only passive detection mode' do it 'returns false' do - %w[wp-admin/install.php wp-login.php].each do |path| - stub_request(:get, target.url(path)).to_return(status: 404) + expect(subject.wordpress?(:passive)).to be false + end + end + + context 'when mixed or aggressive detection modes' do + context 'when wp-admin/install.php and wp-login.php not there' do + it 'returns false' do + %w[wp-admin/install.php wp-login.php].each do |path| + stub_request(:get, target.url(path)).to_return(status: 404) + end + + expect(subject.wordpress?(:mixed)).to be false end - - expect(subject.wordpress?(:mixed)).to be false end - end - context 'when wp-admin/install.php is matching a WP install' do - it 'returns true' do - stub_request(:get, target.url('wp-admin/install.php')) - .to_return(body: File.read(fixtures.join('wp-admin-install.php'))) + context 'when wp-admin/install.php is matching a WP install' do + it 'returns true' do + stub_request(:get, target.url('wp-admin/install.php')) + .to_return(body: File.read(fixtures.join('wp-admin-install.php'))) - expect(subject.wordpress?(:mixed)).to be true + expect(subject.wordpress?(:mixed)).to be true + end end - end - context 'when wp-admin/install.php not there but wp-login.php is matching a WP install' do - it 'returns true' do - stub_request(:get, target.url('wp-admin/install.php')).to_return(status: 404) - stub_request(:get, target.url('wp-login.php')) - .to_return(body: File.read(fixtures.join('wp-login.php'))) + context 'when wp-admin/install.php not there but wp-login.php is matching a WP install' do + it 'returns true' do + stub_request(:get, target.url('wp-admin/install.php')).to_return(status: 404) + stub_request(:get, target.url('wp-login.php')) + .to_return(body: File.read(fixtures.join('wp-login.php'))) - expect(subject.wordpress?(:mixed)).to be true + expect(subject.wordpress?(:mixed)).to be true + end end end end diff --git a/spec/shared_examples/target/platform/wordpress/custom_directories.rb b/spec/shared_examples/target/platform/wordpress/custom_directories.rb index ce4d354c..e822c263 100644 --- a/spec/shared_examples/target/platform/wordpress/custom_directories.rb +++ b/spec/shared_examples/target/platform/wordpress/custom_directories.rb @@ -4,6 +4,9 @@ shared_examples 'WordPress::CustomDirectories' do let(:fixtures) { super().join('custom_directories') } describe '#content_dir' do + # Stub the error_404_res to make it easier to test + before { stub_request(:get, ERROR_404_URL_PATTERN) } + { default: 'wp-content', https: 'wp-content', custom_w_spaces: 'custom content spaces', relative_one: 'wp-content', relative_two: 'wp-content', cache: 'wp-content', @@ -45,9 +48,9 @@ shared_examples 'WordPress::CustomDirectories' do end end - context 'when not found via the homepage' do + context 'when not found via the homepage or 404' do before do - stub_request(:get, target.url).to_return(body: '') + stub_request(:get, target.url) expect(target).to receive(:default_content_dir_exists?).and_return(dir_exist) end @@ -123,6 +126,9 @@ shared_examples 'WordPress::CustomDirectories' do end describe '#sub_dir' do + # Stub the error_404_res to make it easier to test + before { stub_request(:get, ERROR_404_URL_PATTERN) } + { default: false, with_sub_dir: 'wp', relative_two_sub_dir: 'cms' }.each do |file, expected| it "returns #{expected} for #{file}.html" do fixture = File.join(fixtures, "#{file}.html") diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9dfe59b1..16218e07 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -102,5 +102,6 @@ SPECS = Pathname.new(__FILE__).dirname FIXTURES = SPECS.join('fixtures') FINDERS_FIXTURES = FIXTURES.join('finders') DYNAMIC_FINDERS_FIXTURES = FIXTURES.join('dynamic_finders') +ERROR_404_URL_PATTERN = %r{/[a-z\d]{7}\.html$} redefine_constant(:DB_DIR, FIXTURES.join('db'))