From ae343b8cb0aa861b3866536840de7b46e5f426ee Mon Sep 17 00:00:00 2001 From: erwanlr Date: Fri, 12 Apr 2019 13:55:40 +0100 Subject: [PATCH] Checks for wp-content directly (depends on detection-mode) when not identified passively --- app/controllers/custom_directories.rb | 2 +- .../platform/wordpress/custom_directories.rb | 13 ++++- lib/wpscan/version.rb | 2 +- .../controllers/custom_directories_spec.rb | 4 +- .../platform/wordpress/custom_directories.rb | 55 +++++++++++++++++++ 5 files changed, 71 insertions(+), 5 deletions(-) diff --git a/app/controllers/custom_directories.rb b/app/controllers/custom_directories.rb index d7c111ae..4cbdee9b 100644 --- a/app/controllers/custom_directories.rb +++ b/app/controllers/custom_directories.rb @@ -16,7 +16,7 @@ module WPScan target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir - return if target.content_dir + return if target.content_dir(ParsedCli.detection_mode) raise Error::WpContentDirNotDetected end diff --git a/lib/wpscan/target/platform/wordpress/custom_directories.rb b/lib/wpscan/target/platform/wordpress/custom_directories.rb index 1fbdbddc..34d044da 100644 --- a/lib/wpscan/target/platform/wordpress/custom_directories.rb +++ b/lib/wpscan/target/platform/wordpress/custom_directories.rb @@ -13,8 +13,9 @@ module WPScan @plugins_dir = dir.chomp('/') end + # @param [ Symbol ] detection_mode # @return [ String ] The wp-content directory - def content_dir + def content_dir(detection_mode = :mixed) unless @content_dir escaped_url = Regexp.escape(url).gsub(/https?/i, 'https?') pattern = %r{#{escaped_url}([\w\s\-\/]+)\/(?:themes|plugins|uploads|cache)\/}i @@ -26,11 +27,21 @@ module WPScan xpath_pattern_from_page('//script[not(@src)]', pattern, homepage_res) do |match| return @content_dir = match[1] end + + unless detection_mode == :passive + return @content_dir = 'wp-content' if default_content_dir_exists? + end end @content_dir end + def default_content_dir_exists? + # url('wp-content') can't be used here as the folder has not yet been identified + # and the method would try to replace it by nil which would raise an error + [200, 401, 403].include?(Browser.forge_request(uri.join('wp-content/').to_s, head_or_get_params).run.code) + end + # @return [ Addressable::URI ] def content_uri uri.join("#{content_dir}/") diff --git a/lib/wpscan/version.rb b/lib/wpscan/version.rb index 3e9bc6db..a387357a 100644 --- a/lib/wpscan/version.rb +++ b/lib/wpscan/version.rb @@ -2,5 +2,5 @@ # Version module WPScan - VERSION = '3.5.2' + VERSION = '3.5.3' end diff --git a/spec/app/controllers/custom_directories_spec.rb b/spec/app/controllers/custom_directories_spec.rb index 5d165112..828b46f0 100644 --- a/spec/app/controllers/custom_directories_spec.rb +++ b/spec/app/controllers/custom_directories_spec.rb @@ -19,8 +19,8 @@ describe WPScan::Controller::CustomDirectories do end describe '#before_scan' do - context 'when the content_dir is not found and not supply' do - before { expect(controller.target).to receive(:content_dir) } + context 'when the content_dir is not found and not supplied' do + before { expect(controller.target).to receive(:content_dir).with(:mixed) } it 'raises an exception' do expect { controller.before_scan }.to raise_error(WPScan::Error::WpContentDirNotDetected) diff --git a/spec/shared_examples/target/platform/wordpress/custom_directories.rb b/spec/shared_examples/target/platform/wordpress/custom_directories.rb index 942b9507..7d397155 100644 --- a/spec/shared_examples/target/platform/wordpress/custom_directories.rb +++ b/spec/shared_examples/target/platform/wordpress/custom_directories.rb @@ -15,6 +15,61 @@ shared_examples 'WordPress::CustomDirectories' do expect(target.content_dir).to eql expected end end + + context 'when not found via the homepage' do + before { stub_request(:get, target.url).to_return(body: '') } + + context 'when detection mode is passive' do + it 'returns nil' do + expect(target).not_to receive(:default_content_dir_exist?) + + expect(target.content_dir(:passive)).to eql nil + end + end + + context 'when detection mode is mixed or aggressive' do + before { expect(target).to receive(:default_content_dir_exists?).and_return(dir_exist) } + + %i[mixed aggressive].each do |mode| + context 'when default content dir exists' do + let(:dir_exist) { true } + + it 'returns wp-content' do + expect(target.content_dir(mode)).to eql 'wp-content' + end + end + + context 'when default content dir does not exist' do + let(:dir_exist) { false } + + it 'returns nil' do + expect(target.content_dir(mode)).to eql nil + end + end + end + end + end + end + + describe 'default_content_dir_exists?' do + before do + expect(target).to receive(:head_or_get_params).and_return(method: :head) + stub_request(:head, target.uri.join('wp-content/').to_s).to_return(status: status) + end + + context 'when 404' do + let(:status) { 404 } + + its(:default_content_dir_exists?) { should be false } + end + + context 'when 200, 401 or 403' do + [200, 401, 403].each do |code| + let(:status) { code } + + its(:default_content_dir_exists?) { should be true } + end + end end describe '#content_dir=, #plugins_dir=' do