From 72d699b39aaae8c62a6e95c689c3658bd7e81ed5 Mon Sep 17 00:00:00 2001 From: erwanlr Date: Thu, 13 Feb 2020 15:36:42 +0000 Subject: [PATCH] Adds more improvements re passive scanning when there are a lot of urls --- .../interesting_findings/mu_plugins.rb | 2 +- .../main_theme/css_style_in_homepage.rb | 2 +- app/finders/users/author_id_brute_forcing.rb | 14 +++--- app/finders/users/author_posts.rb | 2 +- app/finders/wp_items/urls_in_page.rb | 8 ++- lib/wpscan/target/platform/wordpress.rb | 2 +- .../interesting_findings/mu_plugins_spec.rb | 49 ++++++++++++++++++- .../users/author_id_brute_forcing_spec.rb | 15 +++++- spec/app/finders/users/author_posts_spec.rb | 31 +++++++++--- spec/cache/.gitignore | 2 +- .../mu_plugins/match_href.html | 16 ++++++ .../mu_plugins/match_src.html | 16 ++++++ .../mu_plugins/no_match.html | 13 +++++ .../4.1.1-permalink.html | 6 ++- .../{matches.html => match_href.html} | 4 -- .../wordpress/wordpress_hosted/match_src.html | 12 +++++ .../finders/wp_items/urls_in_page.rb | 30 ++++++++---- .../target/platform/wordpress.rb | 28 +++++++++-- 18 files changed, 214 insertions(+), 38 deletions(-) create mode 100644 spec/fixtures/finders/interesting_findings/mu_plugins/match_href.html create mode 100644 spec/fixtures/finders/interesting_findings/mu_plugins/match_src.html create mode 100644 spec/fixtures/finders/interesting_findings/mu_plugins/no_match.html rename spec/fixtures/target/platform/wordpress/wordpress_hosted/{matches.html => match_href.html} (96%) create mode 100644 spec/fixtures/target/platform/wordpress/wordpress_hosted/match_src.html diff --git a/app/finders/interesting_findings/mu_plugins.rb b/app/finders/interesting_findings/mu_plugins.rb index 4e32a946..88892b6e 100644 --- a/app/finders/interesting_findings/mu_plugins.rb +++ b/app/finders/interesting_findings/mu_plugins.rb @@ -9,7 +9,7 @@ module WPScan def passive(_opts = {}) pattern = %r{#{target.content_dir}/mu\-plugins/}i - target.in_scope_uris(target.homepage_res) do |uri| + target.in_scope_uris(target.homepage_res, '(//@href|//@src)[contains(., "mu-plugins")]') do |uri| next unless uri.path&.match?(pattern) url = target.url('wp-content/mu-plugins/') diff --git a/app/finders/main_theme/css_style_in_homepage.rb b/app/finders/main_theme/css_style_in_homepage.rb index 34bb8619..ed0eab7f 100644 --- a/app/finders/main_theme/css_style_in_homepage.rb +++ b/app/finders/main_theme/css_style_in_homepage.rb @@ -20,7 +20,7 @@ module WPScan end def passive_from_css_href(res, opts) - target.in_scope_uris(res, '//style/@src|//link/@href') do |uri| + target.in_scope_uris(res, '//link/@href[contains(., "style.css")]') do |uri| next unless uri.path =~ %r{/themes/([^\/]+)/style.css\z}i return create_theme(Regexp.last_match[1], uri.to_s, opts) diff --git a/app/finders/users/author_id_brute_forcing.rb b/app/finders/users/author_id_brute_forcing.rb index d7d1ca3b..ee70ac4b 100644 --- a/app/finders/users/author_id_brute_forcing.rb +++ b/app/finders/users/author_id_brute_forcing.rb @@ -71,11 +71,13 @@ module WPScan return username, 'Display Name', 50 if username end - # @param [ String ] url + # @param [ String, Addressable::URI ] uri # # @return [ String, nil ] - def username_from_author_url(url) - url[%r{/author/([^/\b]+)/?}i, 1] + def username_from_author_url(uri) + uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI) + + uri.path[%r{/author/([^/\b]+)/?}i, 1] end # @param [ Typhoeus::Response ] res @@ -83,12 +85,12 @@ module WPScan # @return [ String, nil ] The username found def username_from_response(res) # Permalink enabled - target.in_scope_uris(res, '//link/@href|//a/@href') do |uri| - username = username_from_author_url(uri.to_s) + target.in_scope_uris(res, '//@href[contains(., "author/")]') do |uri| + username = username_from_author_url(uri) return username if username end - # No permalink + # No permalink, TODO Maybe use xpath to extract the classes ? res.body[/ ] The plugins/themes detected in the href, src attributes of the homepage + # @return [ Array ] The plugins/themes detected in the href, src attributes of the page def items_from_links(type, uniq = true) found = [] + xpath = format( + '(//@href|//@src|//@data-src)[contains(., "%s")]', + type == 'plugins' ? target.plugins_dir : target.content_dir + ) - target.in_scope_uris(page_res) do |uri| + target.in_scope_uris(page_res, xpath) do |uri| next unless uri.to_s =~ item_attribute_pattern(type) slug = Regexp.last_match[1]&.strip diff --git a/lib/wpscan/target/platform/wordpress.rb b/lib/wpscan/target/platform/wordpress.rb index df7d621f..2b60a174 100644 --- a/lib/wpscan/target/platform/wordpress.rb +++ b/lib/wpscan/target/platform/wordpress.rb @@ -100,7 +100,7 @@ module WPScan unless content_dir pattern = %r{https?://s\d\.wp\.com#{WORDPRESS_PATTERN}}i.freeze - xpath = '//@href[contains(., "wp.com")]|//@src[contains(., "wp.com")]' + xpath = '(//@href|//@src)[contains(., "wp.com")]' uris_from_page(homepage_res, xpath) do |uri| return true if uri.to_s.match?(pattern) diff --git a/spec/app/finders/interesting_findings/mu_plugins_spec.rb b/spec/app/finders/interesting_findings/mu_plugins_spec.rb index bf928a96..443eaac9 100644 --- a/spec/app/finders/interesting_findings/mu_plugins_spec.rb +++ b/spec/app/finders/interesting_findings/mu_plugins_spec.rb @@ -6,8 +6,55 @@ describe WPScan::Finders::InterestingFindings::MuPlugins do let(:url) { 'http://ex.lo/' } let(:fixtures) { FINDERS_FIXTURES.join('interesting_findings', 'mu_plugins') } + before do + expect(target).to receive(:content_dir).at_least(1).and_return('wp-content') + end + describe '#passive' do - xit + before { stub_request(:get, url).to_return(body: body) } + + context 'when no uris' do + let(:body) { '' } + + its(:passive) { should be nil } + end + + context 'when a large amount of unrelated uris' do + let(:body) do + Array.new(250) { |i| "Some Link" }.join("\n") + end + + it 'should not take a while to process the page' do + time_start = Time.now + result = finder.passive + time_end = Time.now + + expect(result).to be nil + expect(time_end - time_start).to be < 1 + end + end + + context 'when uris' do + let(:body) { File.read(fixtures.join(fixture)) } + + context 'when none matching' do + let(:fixture) { 'no_match.html' } + + its(:passive) { should be nil } + end + + context 'when matching via href' do + let(:fixture) { 'match_href.html' } + + its(:passive) { should be_a WPScan::Model::MuPlugins } + end + + context 'when matching from src' do + let(:fixture) { 'match_src.html' } + + its(:passive) { should be_a WPScan::Model::MuPlugins } + end + end end describe '#aggressive' do diff --git a/spec/app/finders/users/author_id_brute_forcing_spec.rb b/spec/app/finders/users/author_id_brute_forcing_spec.rb index 517b7dcf..a9b4c311 100644 --- a/spec/app/finders/users/author_id_brute_forcing_spec.rb +++ b/spec/app/finders/users/author_id_brute_forcing_spec.rb @@ -19,7 +19,7 @@ describe WPScan::Finders::Users::AuthorIdBruteForcing do end end - describe '#potential_username' do + describe '#username_from_response' do [ '4.1.1', '4.1.1-permalink', '3.0', '3.0-permalink', @@ -32,6 +32,19 @@ describe WPScan::Finders::Users::AuthorIdBruteForcing do expect(finder.username_from_response(res)).to eql 'admin' end end + + context 'when a lot of unrelated links' do + it 'should not take a while to process the page' do + body = Array.new(300) { |i| "Some Link" }.join("\n") + body << 'Link' + + time_start = Time.now + expect(finder.username_from_response(Typhoeus::Response.new(body: body))).to eql 'test' + time_end = Time.now + + expect(time_end - time_start).to be < 1 + end + end end describe '#display_name_from_body' do diff --git a/spec/app/finders/users/author_posts_spec.rb b/spec/app/finders/users/author_posts_spec.rb index 4bca4e3f..ea5d5b3b 100644 --- a/spec/app/finders/users/author_posts_spec.rb +++ b/spec/app/finders/users/author_posts_spec.rb @@ -16,12 +16,31 @@ describe WPScan::Finders::Users::AuthorPosts do results = finder.potential_usernames(res) - expect(results).to eql([ - ['admin', 'Author Pattern', 100], - ['admin display_name', 'Display Name', 30], - ['editor', 'Author Pattern', 100], - ['editor', 'Display Name', 30] - ]) + expect(results).to eql [ + ['admin', 'Author Pattern', 100], + ['admin display_name', 'Display Name', 30], + ['editor', 'Author Pattern', 100], + ['editor', 'Display Name', 30] + ] + end + + context 'when a lot of unrelated uris' do + it 'should not take a while to process the page' do + body = Array.new(300) { |i| "Some Link" }.join("\n") + body << "Other Link" + body << "user display name" + + time_start = Time.now + results = finder.potential_usernames(Typhoeus::Response.new(body: body)) + time_end = Time.now + + expect(results).to eql [ + ['admin', 'Author Pattern', 100], + ['user display name', 'Display Name', 30] + ] + + expect(time_end - time_start).to be < 1 + end end end end diff --git a/spec/cache/.gitignore b/spec/cache/.gitignore index 86d0cb27..5e7d2734 100644 --- a/spec/cache/.gitignore +++ b/spec/cache/.gitignore @@ -1,4 +1,4 @@ # Ignore everything in this directory * # Except this file -!.gitignore \ No newline at end of file +!.gitignore diff --git a/spec/fixtures/finders/interesting_findings/mu_plugins/match_href.html b/spec/fixtures/finders/interesting_findings/mu_plugins/match_href.html new file mode 100644 index 00000000..a5506aa8 --- /dev/null +++ b/spec/fixtures/finders/interesting_findings/mu_plugins/match_href.html @@ -0,0 +1,16 @@ + + + + + + + + + WP 5.3.2 | Just another WordPress site + + + + + + + diff --git a/spec/fixtures/finders/interesting_findings/mu_plugins/match_src.html b/spec/fixtures/finders/interesting_findings/mu_plugins/match_src.html new file mode 100644 index 00000000..75d36ed2 --- /dev/null +++ b/spec/fixtures/finders/interesting_findings/mu_plugins/match_src.html @@ -0,0 +1,16 @@ + + + + + + + + + WP 5.3.2 | Just another WordPress site + + + + + + + diff --git a/spec/fixtures/finders/interesting_findings/mu_plugins/no_match.html b/spec/fixtures/finders/interesting_findings/mu_plugins/no_match.html new file mode 100644 index 00000000..3aa6b59a --- /dev/null +++ b/spec/fixtures/finders/interesting_findings/mu_plugins/no_match.html @@ -0,0 +1,13 @@ + + + + + + + + + WP 5.3.2 | Just another WordPress site + + + + diff --git a/spec/fixtures/finders/users/author_id_brute_forcing/4.1.1-permalink.html b/spec/fixtures/finders/users/author_id_brute_forcing/4.1.1-permalink.html index 4a658b68..f94e00e9 100644 --- a/spec/fixtures/finders/users/author_id_brute_forcing/4.1.1-permalink.html +++ b/spec/fixtures/finders/users/author_id_brute_forcing/4.1.1-permalink.html @@ -15,8 +15,12 @@ - + + +Link + diff --git a/spec/fixtures/target/platform/wordpress/wordpress_hosted/matches.html b/spec/fixtures/target/platform/wordpress/wordpress_hosted/match_href.html similarity index 96% rename from spec/fixtures/target/platform/wordpress/wordpress_hosted/matches.html rename to spec/fixtures/target/platform/wordpress/wordpress_hosted/match_href.html index bb91c4ca..5e6e3fac 100644 --- a/spec/fixtures/target/platform/wordpress/wordpress_hosted/matches.html +++ b/spec/fixtures/target/platform/wordpress/wordpress_hosted/match_href.html @@ -215,10 +215,6 @@ div.break_footer document.adoffset = 0; document.adPopupFile = '/cnn_adspaces/adsPopup2.html'; - - - - + + + diff --git a/spec/shared_examples/finders/wp_items/urls_in_page.rb b/spec/shared_examples/finders/wp_items/urls_in_page.rb index b3598a24..abec29ba 100644 --- a/spec/shared_examples/finders/wp_items/urls_in_page.rb +++ b/spec/shared_examples/finders/wp_items/urls_in_page.rb @@ -2,12 +2,14 @@ shared_examples 'App::Finders::WpItems::UrlsInPage' do before do - stub_request(:get, page_url).to_return(body: File.read(fixtures.join(file))) + allow(finder.target).to receive(:content_dir).and_return('wp-content') + + stub_request(:get, page_url).to_return(body: defined?(body) ? body : File.read(fixtures.join(fixture))) end describe '#items_from_links' do context 'when none found' do - let(:file) { 'none.html' } + let(:fixture) { 'none.html' } it 'returns an empty array' do expect(finder.items_from_links(type)).to eql([]) @@ -15,21 +17,31 @@ shared_examples 'App::Finders::WpItems::UrlsInPage' do end context 'when found' do - let(:file) { 'found.html' } + let(:fixture) { 'found.html' } it 'returns the expected array' do - expect(finder.target).to receive(:content_dir).at_least(1).and_return('wp-content') - expect(finder.items_from_links(type, uniq_links)).to eql expected_from_links end end + + context 'when a lof of unrelated links' do + let(:body) do + Array.new(250) { |i| "Link" }.join("\n") + end + + it 'should not take a while to process the page' do + time_start = Time.now + expect(finder.items_from_links(type)).to eql [] + time_end = Time.now + + expect(time_end - time_start).to be < 1 + end + end end describe '#items_from_codes' do - before { expect(finder.target).to receive(:content_dir).at_least(1).and_return('wp-content') } - context 'when none found' do - let(:file) { 'none.html' } + let(:fixture) { 'none.html' } it 'returns an empty array' do expect(finder.items_from_codes(type)).to eql([]) @@ -37,7 +49,7 @@ shared_examples 'App::Finders::WpItems::UrlsInPage' do end context 'when found' do - let(:file) { 'found.html' } + let(:fixture) { 'found.html' } it 'returns the expected array' do expect(finder.items_from_codes(type, uniq_codes)).to eql expected_from_codes diff --git a/spec/shared_examples/target/platform/wordpress.rb b/spec/shared_examples/target/platform/wordpress.rb index 32432996..bcad262c 100644 --- a/spec/shared_examples/target/platform/wordpress.rb +++ b/spec/shared_examples/target/platform/wordpress.rb @@ -172,11 +172,19 @@ shared_examples WPScan::Target::Platform::WordPress do context 'when wp-content not detected' do before do expect(target).to receive(:content_dir).and_return(nil) - stub_request(:get, target.url).to_return(body: File.read(fixtures.join(fixture).to_s)) + + stub_request(:get, target.url) + .to_return(body: defined?(body) ? body : File.read(fixtures.join(fixture).to_s)) end - context 'when an URL matches a WP hosted' do - let(:fixture) { 'matches.html' } + context 'when an src URL matches a WP hosted' do + let(:fixture) { 'match_src.html' } + + its(:wordpress_hosted?) { should be true } + end + + context 'when an href URL matches a WP hosted' do + let(:fixture) { 'match_href.html' } its(:wordpress_hosted?) { should be true } end @@ -186,6 +194,20 @@ shared_examples WPScan::Target::Platform::WordPress do its(:wordpress_hosted?) { should be false } end + + context 'when a lof of unrelated urls' do + let(:body) do + Array.new(250) { |i| "Some Link" }.join("\n") + end + + it 'should not take a while to process the page' do + time_start = Time.now + expect(target.wordpress_hosted?).to be false + time_end = Time.now + + expect(time_end - time_start).to be < 1 + end + end end context 'when wp-content detected' do