From 231f5157bfd9458a03b640bb8a03ceb6969ab023 Mon Sep 17 00:00:00 2001 From: erwanlr Date: Fri, 22 Mar 2019 20:20:07 +0000 Subject: [PATCH] Fixes #1322 --- app/finders/db_exports/known_locations.rb | 15 ++++++++-- .../interesting_findings/tmm_db_migrate.rb | 2 +- .../interesting_findings/upload_sql_dump.rb | 13 +++++---- app/finders/timthumbs/known_locations.rb | 6 ++-- lib/wpscan/finders.rb | 29 +++++-------------- lib/wpscan/target.rb | 9 ++++++ .../db_exports/known_locations_spec.rb | 20 +++++++++---- .../upload_sql_dump_spec.rb | 14 ++++++--- 8 files changed, 63 insertions(+), 45 deletions(-) diff --git a/app/finders/db_exports/known_locations.rb b/app/finders/db_exports/known_locations.rb index 24f26227..1836abba 100644 --- a/app/finders/db_exports/known_locations.rb +++ b/app/finders/db_exports/known_locations.rb @@ -4,7 +4,9 @@ module WPScan # DB Exports finder # See https://github.com/wpscanteam/wpscan-v3/issues/62 class KnownLocations < CMSScanner::Finders::Finder - include CMSScanner::Finders::Finder::Enumerator + include Finders::Finder::Enumerator + + SQL_PATTERN = /(?:DROP|(?:UN)?LOCK|CREATE) TABLE|INSERT INTO/.freeze # @param [ Hash ] opts # @option opts [ String ] :list @@ -15,14 +17,21 @@ module WPScan found = [] enumerate(potential_urls(opts), opts) do |res| - next unless res.code == 200 && res.body =~ /INSERT INTO/ - found << WPScan::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100) end found end + def valid_response?(res, _exclude_content = nil) + return false unless res.code == 200 + + return true if res.effective_url.end_with?('.zip') && + res.headers['Content-Type'] =~ %r{\Aapplication/zip}i + + Browser.get(res.effective_url, headers: { 'Range' => 'bytes=0-3000' }).body =~ SQL_PATTERN ? true : false + end + # @param [ Hash ] opts # @option opts [ String ] :list Mandatory # diff --git a/app/finders/interesting_findings/tmm_db_migrate.rb b/app/finders/interesting_findings/tmm_db_migrate.rb index d388f6e0..787140ba 100644 --- a/app/finders/interesting_findings/tmm_db_migrate.rb +++ b/app/finders/interesting_findings/tmm_db_migrate.rb @@ -7,7 +7,7 @@ module WPScan def aggressive(_opts = {}) path = 'wp-content/uploads/tmm_db_migrate/tmm_db_migrate.zip' url = target.url(path) - res = Browser.get(url) + res = browser.forge_request(url, target.head_or_get_request_params).run return unless res.code == 200 && res.headers['Content-Type'] =~ %r{\Aapplication/zip}i diff --git a/app/finders/interesting_findings/upload_sql_dump.rb b/app/finders/interesting_findings/upload_sql_dump.rb index 9d45398f..7119088d 100644 --- a/app/finders/interesting_findings/upload_sql_dump.rb +++ b/app/finders/interesting_findings/upload_sql_dump.rb @@ -3,24 +3,25 @@ module WPScan module InterestingFindings # UploadSQLDump finder class UploadSQLDump < CMSScanner::Finders::Finder - SQL_PATTERN = /(?:(?:(?:DROP|CREATE) TABLE)|INSERT INTO)/.freeze + SQL_PATTERN = /(?:DROP|CREATE|(?:UN)?LOCK) TABLE|INSERT INTO/.freeze # @return [ InterestingFinding ] def aggressive(_opts = {}) - url = dump_url - res = Browser.get(url) + head_res = browser.forge_request(dump_url, target.head_or_get_request_params).run - return unless res.code == 200 && res.body =~ SQL_PATTERN + return unless head_res.code == 200 + + return unless Browser.get(dump_url, headers: { 'Range' => 'bytes=0-3000' }).body =~ SQL_PATTERN WPScan::UploadSQLDump.new( - url, + dump_url, confidence: 100, found_by: DIRECT_ACCESS ) end def dump_url - target.url('wp-content/uploads/dump.sql') + @dump_url ||= target.url('wp-content/uploads/dump.sql') end end end diff --git a/app/finders/timthumbs/known_locations.rb b/app/finders/timthumbs/known_locations.rb index e6923436..a0ebc64a 100644 --- a/app/finders/timthumbs/known_locations.rb +++ b/app/finders/timthumbs/known_locations.rb @@ -22,15 +22,13 @@ module WPScan end # @param [ Typhoeus::Response ] res - # @param [ Regexp,nil ] exclude_content + # @param [ Regexp, nil ] exclude_content # # @return [ Boolean ] def valid_response?(res, _exclude_content = nil) return false unless res.code == 400 - full_res = Browser.get(res.effective_url, cache_ttl: 0) - - full_res.body =~ /no image specified/i ? true : false + Browser.get(res.effective_url).body =~ /no image specified/i ? true : false end # @param [ Hash ] opts diff --git a/lib/wpscan/finders.rb b/lib/wpscan/finders.rb index c191f5f6..97bc25ef 100644 --- a/lib/wpscan/finders.rb +++ b/lib/wpscan/finders.rb @@ -39,9 +39,6 @@ module WPScan # # @yield [ Typhoeus::Response, String ] def enumerate(urls, opts = {}) - determine_request_params(urls, opts) - # determine_valid_response_codes(opts) - create_progress_bar(opts.merge(total: urls.size)) urls.each do |url, slug| @@ -59,6 +56,11 @@ module WPScan hydra.run end + # @return [ Hash ] + def request_params + @request_params ||= target.head_or_get_request_params.merge(cache_ttl: 0) + end + # @param [ Typhoeus::Response ] res # @param [ Regexp,nil ] exclude_content # @@ -71,7 +73,9 @@ module WPScan # Perform a full get to check if homepage or custom 404 if res.code == 200 - full_res = Browser.get(res.effective_url, cache_ttl: 0) + # The cache is not disabled to avoid additional request/s when checking + # for directory listing + full_res = Browser.get(res.effective_url) return false if target.homepage_or_404?(full_res) || exclude_content && full_res.body.match(exclude_content) @@ -81,23 +85,6 @@ module WPScan end # rubocop:enable Metrics/PerceivedComplexity - # @return [ Hash ] - def request_params - @request_params ||= { cache_ttl: 0 } - end - - # @param [ Hash ] urls - # @param [ Hash ] opts - def determine_request_params(urls, _opts) - head_res = Browser.head(urls.first[0], cache_ttl: 0) - - @request_params = if head_res.code == 405 - { method: :get, maxfilesize: 1, cache_ttl: 0 } - else - { method: :head, cache_ttl: 0 } - end - end - # @return [ Array ] def valid_response_codes @valid_response_codes ||= [200, 401, 403, 301] diff --git a/lib/wpscan/target.rb b/lib/wpscan/target.rb index 4adea28a..b23cf53a 100644 --- a/lib/wpscan/target.rb +++ b/lib/wpscan/target.rb @@ -5,6 +5,15 @@ module WPScan class Target < CMSScanner::Target include Platform::WordPress + # @return [ Hash ] + def head_or_get_request_params + @head_or_get_request_params ||= if Browser.head(url).code == 405 + { method: :get, maxfilesize: 1 } + else + { method: :head } + end + end + # @return [ Boolean ] def vulnerable? [@wp_version, @main_theme, @plugins, @themes, @timthumbs].each do |e| diff --git a/spec/app/finders/db_exports/known_locations_spec.rb b/spec/app/finders/db_exports/known_locations_spec.rb index bd58713e..080b1e73 100644 --- a/spec/app/finders/db_exports/known_locations_spec.rb +++ b/spec/app/finders/db_exports/known_locations_spec.rb @@ -25,10 +25,10 @@ describe WPScan::Finders::DbExports::KnownLocations do describe '#aggressive' do before do expect(target).to receive(:sub_dir).at_least(1).and_return(false) - expect(target).to receive(:homepage_or_404?).at_least(1).and_return(false) + expect(target).to receive(:head_or_get_request_params).and_return(method: :head) finder.potential_urls(opts).each_key do |url| - stub_request(:get, url).to_return(status: 404) + stub_request(:head, url).to_return(status: 404) end end @@ -38,20 +38,28 @@ describe WPScan::Finders::DbExports::KnownLocations do end end + context 'when a zip returns a 200' do + xit + end + context 'when some files exist' do - let(:files) { %w[ex.sql backups/db_backup.sql] } + let(:found_files) { %w[ex.sql backups/db_backup.sql] } let(:db_export) { File.read(fixtures.join('dump.sql')) } before do - files.each do |file| - stub_request(:get, "#{url}#{file}").to_return(body: db_export) + found_files.each do |file| + stub_request(:head, "#{url}#{file}").to_return(status: 200) + + stub_request(:get, "#{url}#{file}") + .with(headers: { 'Range' => 'bytes=0-3000' }) + .to_return(body: db_export) end end it 'returns the expected Array' do expected = [] - files.each do |file| + found_files.each do |file| url = "#{target.url}#{file}" expected << WPScan::DbExport.new( url, diff --git a/spec/app/finders/interesting_findings/upload_sql_dump_spec.rb b/spec/app/finders/interesting_findings/upload_sql_dump_spec.rb index c8881604..a83718ba 100644 --- a/spec/app/finders/interesting_findings/upload_sql_dump_spec.rb +++ b/spec/app/finders/interesting_findings/upload_sql_dump_spec.rb @@ -6,13 +6,16 @@ describe WPScan::Finders::InterestingFindings::UploadSQLDump do let(:wp_content) { 'wp-content' } describe '#aggressive' do - before { expect(target).to receive(:content_dir).at_least(1).and_return(wp_content) } + before do + expect(target).to receive(:content_dir).at_least(1).and_return(wp_content) + expect(target).to receive(:head_or_get_request_params).and_return(method: :head) + end - after { expect(finder.aggressive).to eql @expected } + after { expect(finder.aggressive).to eql @expected } context 'when not a 200' do it 'returns nil' do - stub_request(:get, finder.dump_url).to_return(status: 404) + stub_request(:head, finder.dump_url).to_return(status: 404) @expected = nil end @@ -20,8 +23,11 @@ describe WPScan::Finders::InterestingFindings::UploadSQLDump do context 'when a 200' do before do + stub_request(:head, finder.dump_url).to_return(status: 200) + stub_request(:get, finder.dump_url) - .to_return(status: 200, body: File.read(fixtures.join(fixture))) + .with(headers: { 'Range' => 'bytes=0-3000' }) + .to_return(body: File.read(fixtures.join(fixture))) end context 'when the body does not match a SQL dump' do