From 5f2b8f8a2eb7a76817f1ce81df4d572ca7eaa9b0 Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Wed, 20 Mar 2019 07:47:28 +0000
Subject: [PATCH 01/13] Fixes #1317
---
app/finders/users/rss_generator.rb | 10 +++++-----
.../finders/users/rss_generator/feed.xml | 18 ++++++++++++++++++
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/app/finders/users/rss_generator.rb b/app/finders/users/rss_generator.rb
index dc936ddc..f090acb0 100644
--- a/app/finders/users/rss_generator.rb
+++ b/app/finders/users/rss_generator.rb
@@ -17,20 +17,20 @@ module WPScan
begin
res.xml.xpath('//item/dc:creator').each do |node|
- potential_username = node.text.to_s
+ username = node.text.to_s
# Ignoring potential username longer than 60 characters and containing accents
# as they are considered invalid. See https://github.com/wpscanteam/wpscan/issues/1215
- next if potential_username.length > 60 || potential_username =~ /[^\x00-\x7F]/
+ next if username.strip.empty? || username.length > 60 || username =~ /[^\x00-\x7F]/
- potential_usernames << potential_username
+ potential_usernames << username
end
rescue Nokogiri::XML::XPath::SyntaxError
next
end
- potential_usernames.uniq.each do |potential_username|
- found << CMSScanner::User.new(potential_username, found_by: found_by, confidence: 50)
+ potential_usernames.uniq.each do |username|
+ found << CMSScanner::User.new(username, found_by: found_by, confidence: 50)
end
break
diff --git a/spec/fixtures/finders/users/rss_generator/feed.xml b/spec/fixtures/finders/users/rss_generator/feed.xml
index a9ab6453..46841668 100644
--- a/spec/fixtures/finders/users/rss_generator/feed.xml
+++ b/spec/fixtures/finders/users/rss_generator/feed.xml
@@ -59,5 +59,23 @@
Michael Schrage is a researcher at the MIT Sloan School of Management Initiative on the Digital Economy, where he does research and advisory work on how digital media transforms agency, human capital, and innovation.
]]>
+
+
+ Hello world!
+ http://ex.lo/2018/09/23/hello-world/
+ http://ex.lo/2018/09/23/hello-world/#comments
+ Sun, 23 Sep 2018 11:31:56 +0000
+
+
+
+
+
+ Hello world!
+ http://ex.lo/2018/09/23/hello-world/
+ http://ex.lo/2018/09/23/hello-world/#comments
+ Sun, 23 Sep 2018 11:31:56 +0000
+
+
+
From 6304fe4c19c8fec196dcea952544e2a63bd6b475 Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Wed, 20 Mar 2019 08:41:39 +0000
Subject: [PATCH 02/13] Fixes #1318
---
lib/wpscan/target/platform/wordpress/custom_directories.rb | 2 +-
.../wordpress/custom_directories/relative_two_sub_dir.html | 6 ++++++
.../target/platform/wordpress/custom_directories.rb | 4 ++--
3 files changed, 9 insertions(+), 3 deletions(-)
create mode 100644 spec/fixtures/target/platform/wordpress/custom_directories/relative_two_sub_dir.html
diff --git a/lib/wpscan/target/platform/wordpress/custom_directories.rb b/lib/wpscan/target/platform/wordpress/custom_directories.rb
index 0aba4577..17be3d49 100644
--- a/lib/wpscan/target/platform/wordpress/custom_directories.rb
+++ b/lib/wpscan/target/platform/wordpress/custom_directories.rb
@@ -15,7 +15,7 @@ module WPScan
def content_dir
unless @content_dir
escaped_url = Regexp.escape(url).gsub(/https?/i, 'https?')
- pattern = %r{#{escaped_url}([^\/]+)\/(?:themes|plugins|uploads|cache)\/}i
+ pattern = %r{#{escaped_url}([\w\s\-\/]+)\/(?:themes|plugins|uploads|cache)\/}i
in_scope_urls(homepage_res) do |url|
return @content_dir = Regexp.last_match[1] if url.match(pattern)
diff --git a/spec/fixtures/target/platform/wordpress/custom_directories/relative_two_sub_dir.html b/spec/fixtures/target/platform/wordpress/custom_directories/relative_two_sub_dir.html
new file mode 100644
index 00000000..872d5736
--- /dev/null
+++ b/spec/fixtures/target/platform/wordpress/custom_directories/relative_two_sub_dir.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/spec/shared_examples/target/platform/wordpress/custom_directories.rb b/spec/shared_examples/target/platform/wordpress/custom_directories.rb
index 9d4cfe67..d08d03a5 100644
--- a/spec/shared_examples/target/platform/wordpress/custom_directories.rb
+++ b/spec/shared_examples/target/platform/wordpress/custom_directories.rb
@@ -5,7 +5,7 @@ shared_examples 'WordPress::CustomDirectories' do
{
default: 'wp-content', https: 'wp-content', custom_w_spaces: 'custom content spaces',
relative_one: 'wp-content', relative_two: 'wp-content', cache: 'wp-content',
- in_raw_js: 'wp-content', with_sub_dir: 'app'
+ in_raw_js: 'wp-content', with_sub_dir: 'app', relative_two_sub_dir: 'cms/wp-content'
}.each do |file, expected|
it "returns #{expected} for #{file}.html" do
stub_request(:get, target.url).to_return(body: File.read(fixtures.join("#{file}.html")))
@@ -47,7 +47,7 @@ shared_examples 'WordPress::CustomDirectories' do
end
describe '#sub_dir' do
- { default: false, with_sub_dir: 'wp' }.each do |file, expected|
+ { 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")
From f09606cfa3ee56a0fac22715313d8e98038b0059 Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Wed, 20 Mar 2019 15:21:02 +0000
Subject: [PATCH 03/13] Fixes #1319
---
app/finders/users/wp_json_api.rb | 10 +++-
spec/app/finders/users/wp_json_api_spec.rb | 55 ++++++++++++++++++-
.../users/wp_json_api/api_url/in_scope.html | 1 +
.../wp_json_api/api_url/in_scope_subdir.html | 6 ++
.../api_url/in_scope_subdir_ignored.html | 6 ++
.../wp_json_api/api_url/out_of_scope.html | 1 +
6 files changed, 77 insertions(+), 2 deletions(-)
create mode 100644 spec/fixtures/finders/users/wp_json_api/api_url/in_scope.html
create mode 100644 spec/fixtures/finders/users/wp_json_api/api_url/in_scope_subdir.html
create mode 100644 spec/fixtures/finders/users/wp_json_api/api_url/in_scope_subdir_ignored.html
create mode 100644 spec/fixtures/finders/users/wp_json_api/api_url/out_of_scope.html
diff --git a/app/finders/users/wp_json_api.rb b/app/finders/users/wp_json_api.rb
index 91b0ea85..a795fc6e 100644
--- a/app/finders/users/wp_json_api.rb
+++ b/app/finders/users/wp_json_api.rb
@@ -53,7 +53,15 @@ module WPScan
# @return [ String ] The URL of the API listing the Users
def api_url
- @api_url ||= target.url('wp-json/wp/v2/users/')
+ return @api_url if @api_url
+
+ target.in_scope_urls(target.homepage_res, "//link[@rel='https://api.w.org/']/@href").each do |url, _tag|
+ uri = Addressable::URI.parse(url.strip)
+
+ return @api_url = uri.join('wp/v2/users/').to_s if uri.path.include?('wp-json')
+ end
+
+ @api_url = target.url('wp-json/wp/v2/users/')
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 6f7e2cb4..3d14c503 100644
--- a/spec/app/finders/users/wp_json_api_spec.rb
+++ b/spec/app/finders/users/wp_json_api_spec.rb
@@ -5,7 +5,10 @@ describe WPScan::Finders::Users::WpJsonApi do
let(:fixtures) { FINDERS_FIXTURES.join('users', 'wp_json_api') }
describe '#aggressive' do
- before { allow(target).to receive(:sub_dir).and_return(false) }
+ before do
+ allow(target).to receive(:sub_dir).and_return(false)
+ allow(finder).to receive(:api_url).and_return(target.url('wp-json/wp/v2/users/'))
+ end
context 'when only one page of results' do
before do
@@ -78,4 +81,54 @@ describe WPScan::Finders::Users::WpJsonApi do
end
end
end
+
+ describe '#api_url' do
+ let(:fixtures) { super().join('api_url') }
+
+ context 'when url in the homepage' do
+ {
+ in_scope: 'https://wp.lab/wp-json/wp/v2/users/',
+ out_of_scope: 'http://wp.lab/wp-json/wp/v2/users/'
+ }.each do |fixture, expected|
+ it "returns #{expected} for #{fixture}.html" do
+ stub_request(:get, target.url).to_return(body: File.read(fixtures.join("#{fixture}.html")))
+
+ expect(finder.api_url).to eql expected
+ end
+ end
+
+ context 'when subdir' do
+ before { allow(target).to receive(:subdir).and_return('cms') }
+
+ {
+ in_scope_subdir: 'https://wp.lab/cms/wp-json/wp/v2/users/',
+ in_scope_subdir_ignored: 'https://wp.lab/wp-json/wp/v2/users/'
+ }.each do |fixture, expected|
+ it "returns #{expected} for #{fixture}.html" do
+ stub_request(:get, target.url).to_return(body: File.read(fixtures.join("#{fixture}.html")))
+
+ expect(finder.api_url).to eql expected
+ end
+ end
+ end
+ end
+
+ context 'when not in the homepage' do
+ before { stub_request(:get, target.url) }
+
+ its(:api_url) { should eql target.url('wp-json/wp/v2/users/') }
+ end
+
+ context 'when api_url already found' do
+ before { allow(target).to receive(:sub_dir).and_return(false) }
+
+ it 'does not check the homepage again' do
+ url = target.url('wp-json/wp/v2/users/')
+
+ finder.instance_variable_set(:@api_url, url)
+
+ expect(finder.api_url).to eql url
+ end
+ end
+ end
end
diff --git a/spec/fixtures/finders/users/wp_json_api/api_url/in_scope.html b/spec/fixtures/finders/users/wp_json_api/api_url/in_scope.html
new file mode 100644
index 00000000..22c182e7
--- /dev/null
+++ b/spec/fixtures/finders/users/wp_json_api/api_url/in_scope.html
@@ -0,0 +1 @@
+
diff --git a/spec/fixtures/finders/users/wp_json_api/api_url/in_scope_subdir.html b/spec/fixtures/finders/users/wp_json_api/api_url/in_scope_subdir.html
new file mode 100644
index 00000000..71525fe0
--- /dev/null
+++ b/spec/fixtures/finders/users/wp_json_api/api_url/in_scope_subdir.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/spec/fixtures/finders/users/wp_json_api/api_url/in_scope_subdir_ignored.html b/spec/fixtures/finders/users/wp_json_api/api_url/in_scope_subdir_ignored.html
new file mode 100644
index 00000000..218372c8
--- /dev/null
+++ b/spec/fixtures/finders/users/wp_json_api/api_url/in_scope_subdir_ignored.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/spec/fixtures/finders/users/wp_json_api/api_url/out_of_scope.html b/spec/fixtures/finders/users/wp_json_api/api_url/out_of_scope.html
new file mode 100644
index 00000000..b793dc25
--- /dev/null
+++ b/spec/fixtures/finders/users/wp_json_api/api_url/out_of_scope.html
@@ -0,0 +1 @@
+
From f414e6eeb75f2e687b63f4a57cb853e1b28c35ee Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Wed, 20 Mar 2019 20:10:30 +0000
Subject: [PATCH 04/13] Better code for WpVersion#all
---
app/models/wp_version.rb | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/app/models/wp_version.rb b/app/models/wp_version.rb
index c14cbd21..439593a4 100644
--- a/app/models/wp_version.rb
+++ b/app/models/wp_version.rb
@@ -23,13 +23,12 @@ module WPScan
@all_numbers = []
DB::Fingerprints.wp_fingerprints.each_value do |fp|
- fp.each_value do |versions|
- versions.each do |version|
- @all_numbers << version unless @all_numbers.include?(version)
- end
- end
+ @all_numbers << fp.values
end
+ # @all_numbers.flatten.uniq.sort! {} doesn't produce the same result here.
+ @all_numbers.flatten!
+ @all_numbers.uniq!
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
end
From 496fc4ebee7c34b898d5054cadfb84d6274e7ed4 Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Wed, 20 Mar 2019 20:12:18 +0000
Subject: [PATCH 05/13] Typo
---
lib/wpscan/db/dynamic_finders/plugin.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/wpscan/db/dynamic_finders/plugin.rb b/lib/wpscan/db/dynamic_finders/plugin.rb
index 16e25a95..59e8de29 100644
--- a/lib/wpscan/db/dynamic_finders/plugin.rb
+++ b/lib/wpscan/db/dynamic_finders/plugin.rb
@@ -60,7 +60,7 @@ module WPScan
# @param [ String ] slug
# @return [ Constant ]
- def self.maybe_create_modudle(slug)
+ def self.maybe_create_module(slug)
# What about slugs such as js_composer which will be done as JsComposer, just like js-composer
constant_name = classify_slug(slug)
@@ -76,7 +76,7 @@ module WPScan
# Kind of an issue here, module is created even if there is no valid classes
# Could put the #maybe_ directly in the #send() BUT it would be checked everytime,
# which is kind of a waste
- mod = maybe_create_modudle(slug)
+ mod = maybe_create_module(slug)
finders.each do |finder_class, config|
klass = config['class'] || finder_class
From 72bddca3143f6f6a414ccb51cab4bb927ce73b5c Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Wed, 20 Mar 2019 21:08:50 +0000
Subject: [PATCH 06/13] Adds profiling binary for dev [WIP] - Ref #1321
---
bin/wpscan-stackprof | 24 ++++++++++++++++++++++++
wpscan.gemspec | 1 +
2 files changed, 25 insertions(+)
create mode 100755 bin/wpscan-stackprof
diff --git a/bin/wpscan-stackprof b/bin/wpscan-stackprof
new file mode 100755
index 00000000..8e25adc6
--- /dev/null
+++ b/bin/wpscan-stackprof
@@ -0,0 +1,24 @@
+#!/usr/bin/env ruby
+
+require 'stackprof' # https://github.com/tmm1/stackprof
+require 'wpscan'
+
+# The object mode produces a segfault currently: https://github.com/jfelchner/ruby-progressbar/issues/153
+# StackProf.run(mode: :object, out: '/tmp/stackprof-object.dump') do
+# StackProf.run(mode: :wall, out: '/tmp/stackprof-wall.dump') do
+StackProf.run(mode: :cpu, out: '/tmp/stackprof-cpu.dump', interval: 500) do
+ # Couldn't we just load the ./wpscan here ?
+ # require_relative 'wpscan' doesn't work
+ WPScan::Scan.new do |s|
+ s.controllers <<
+ WPScan::Controller::CustomDirectories.new <<
+ WPScan::Controller::InterestingFindings.new <<
+ WPScan::Controller::WpVersion.new <<
+ WPScan::Controller::MainTheme.new <<
+ WPScan::Controller::Enumeration.new <<
+ WPScan::Controller::PasswordAttack.new <<
+ WPScan::Controller::Aliases.new
+
+ s.run
+ end
+end
diff --git a/wpscan.gemspec b/wpscan.gemspec
index 8c1ec265..c137d7fd 100644
--- a/wpscan.gemspec
+++ b/wpscan.gemspec
@@ -30,5 +30,6 @@ Gem::Specification.new do |s|
s.add_development_dependency 'rspec-its', '~> 1.2.0'
s.add_development_dependency 'rubocop', '~> 0.66.0'
s.add_development_dependency 'simplecov', '~> 0.16.1'
+ s.add_development_dependency 'stackprof', '~> 0.2.12'
s.add_development_dependency 'webmock', '~> 3.5.1'
end
From c15ff4e32eb8052aff1432f6f725094c2aeb788d Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Thu, 21 Mar 2019 11:31:04 +0000
Subject: [PATCH 07/13] Adds memprof binary - Ref #1321
---
.gitignore | 3 +++
bin/wpscan-memprof | 23 +++++++++++++++++++++++
wpscan.gemspec | 1 +
3 files changed, 27 insertions(+)
create mode 100755 bin/wpscan-memprof
diff --git a/.gitignore b/.gitignore
index 9932d837..c0cf4aea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,6 @@ doc/
# Old files from v2
cache/
data/
+
+# Profiling reports
+bin/memprof*.report
diff --git a/bin/wpscan-memprof b/bin/wpscan-memprof
new file mode 100755
index 00000000..b4c8ce9c
--- /dev/null
+++ b/bin/wpscan-memprof
@@ -0,0 +1,23 @@
+#!/usr/bin/env ruby
+
+require 'memory_profiler' # https://github.com/SamSaffron/memory_profiler
+require 'wpscan'
+
+report = MemoryProfiler.report(top: 10) do
+ # Couldn't we just load the ./wpscan here ?
+ # require_relative 'wpscan' doesn't work
+ WPScan::Scan.new do |s|
+ s.controllers <<
+ WPScan::Controller::CustomDirectories.new <<
+ WPScan::Controller::InterestingFindings.new <<
+ WPScan::Controller::WpVersion.new <<
+ WPScan::Controller::MainTheme.new <<
+ WPScan::Controller::Enumeration.new <<
+ WPScan::Controller::PasswordAttack.new <<
+ WPScan::Controller::Aliases.new
+
+ s.run
+ end
+end
+
+report.pretty_print(scale_bytes: true, detailed_report: true, to_file: 'memprof.report')
diff --git a/wpscan.gemspec b/wpscan.gemspec
index c137d7fd..505edec3 100644
--- a/wpscan.gemspec
+++ b/wpscan.gemspec
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
s.add_development_dependency 'bundler', '>= 1.6'
s.add_development_dependency 'coveralls', '~> 0.8.0'
+ s.add_development_dependency 'memory_profiler', '~> 0.9.12'
s.add_development_dependency 'rake', '~> 12.3'
s.add_development_dependency 'rspec', '~> 3.8.0'
s.add_development_dependency 'rspec-its', '~> 1.2.0'
From 1f0f87633bb8c65caec1e40eb47c5761f64de63b Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Thu, 21 Mar 2019 13:52:34 +0000
Subject: [PATCH 08/13] Reduces memory allocation with creating DFs
---
lib/wpscan/db/dynamic_finders/plugin.rb | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/lib/wpscan/db/dynamic_finders/plugin.rb b/lib/wpscan/db/dynamic_finders/plugin.rb
index 59e8de29..7500f7c2 100644
--- a/lib/wpscan/db/dynamic_finders/plugin.rb
+++ b/lib/wpscan/db/dynamic_finders/plugin.rb
@@ -64,18 +64,24 @@ module WPScan
# What about slugs such as js_composer which will be done as JsComposer, just like js-composer
constant_name = classify_slug(slug)
- unless version_finder_module.constants.include?(constant_name)
+ # version_finder_module.constants.include? could be used here
+ # however, it increases the memory allocated doing so.
+ unless version_finder_modules.include?(constant_name)
version_finder_module.const_set(constant_name, Module.new)
+
+ version_finder_modules << constant_name
end
version_finder_module.const_get(constant_name)
end
+ # @return [ Array ]
+ def self.version_finder_modules
+ @version_finder_modules ||= version_finder_module.constants
+ end
+
def self.create_versions_finders
versions_finders_configs.each do |slug, finders|
- # Kind of an issue here, module is created even if there is no valid classes
- # Could put the #maybe_ directly in the #send() BUT it would be checked everytime,
- # which is kind of a waste
mod = maybe_create_module(slug)
finders.each do |finder_class, config|
From d407815c306dd047ff68e70efe31dc63334e678f Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Thu, 21 Mar 2019 16:54:06 +0000
Subject: [PATCH 09/13] Adds comment about scale_bytes in memory_profiler
---
bin/wpscan-memprof | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/bin/wpscan-memprof b/bin/wpscan-memprof
index b4c8ce9c..cd768494 100755
--- a/bin/wpscan-memprof
+++ b/bin/wpscan-memprof
@@ -20,4 +20,6 @@ report = MemoryProfiler.report(top: 10) do
end
end
-report.pretty_print(scale_bytes: true, detailed_report: true, to_file: 'memprof.report')
+# scale_bytes option not yet supported in the latest stable
+# See https://github.com/SamSaffron/memory_profiler/issues/68
+report.pretty_print($stdout, scale_bytes: true, detailed_report: true, to_file: 'memprof.report')
From 95eb6a732cb3531c714a66503ab85c891ebbcb3a Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Thu, 21 Mar 2019 20:50:57 +0000
Subject: [PATCH 10/13] Memprofiling - Increases the top to be displayed to 15
---
bin/wpscan-memprof | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/bin/wpscan-memprof b/bin/wpscan-memprof
index cd768494..7bcce447 100755
--- a/bin/wpscan-memprof
+++ b/bin/wpscan-memprof
@@ -3,9 +3,7 @@
require 'memory_profiler' # https://github.com/SamSaffron/memory_profiler
require 'wpscan'
-report = MemoryProfiler.report(top: 10) do
- # Couldn't we just load the ./wpscan here ?
- # require_relative 'wpscan' doesn't work
+report = MemoryProfiler.report(top: 15) do
WPScan::Scan.new do |s|
s.controllers <<
WPScan::Controller::CustomDirectories.new <<
From 8b18204a697d5255823d4fca197fb30aa3f19bd1 Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Fri, 22 Mar 2019 06:56:10 +0000
Subject: [PATCH 11/13] Updates memory_profiler dep, revert changes to memory
allocated commit (increased retained memory too much)
---
bin/wpscan-memprof | 4 +---
lib/wpscan/db/dynamic_finders/plugin.rb | 11 +----------
wpscan.gemspec | 2 +-
3 files changed, 3 insertions(+), 14 deletions(-)
diff --git a/bin/wpscan-memprof b/bin/wpscan-memprof
index 7bcce447..2397ce7e 100755
--- a/bin/wpscan-memprof
+++ b/bin/wpscan-memprof
@@ -18,6 +18,4 @@ report = MemoryProfiler.report(top: 15) do
end
end
-# scale_bytes option not yet supported in the latest stable
-# See https://github.com/SamSaffron/memory_profiler/issues/68
-report.pretty_print($stdout, scale_bytes: true, detailed_report: true, to_file: 'memprof.report')
+report.pretty_print(scale_bytes: true, to_file: 'memprof.report')
diff --git a/lib/wpscan/db/dynamic_finders/plugin.rb b/lib/wpscan/db/dynamic_finders/plugin.rb
index 7500f7c2..a839c00f 100644
--- a/lib/wpscan/db/dynamic_finders/plugin.rb
+++ b/lib/wpscan/db/dynamic_finders/plugin.rb
@@ -64,22 +64,13 @@ module WPScan
# What about slugs such as js_composer which will be done as JsComposer, just like js-composer
constant_name = classify_slug(slug)
- # version_finder_module.constants.include? could be used here
- # however, it increases the memory allocated doing so.
- unless version_finder_modules.include?(constant_name)
+ unless version_finder_module.constants.include?(constant_name)
version_finder_module.const_set(constant_name, Module.new)
-
- version_finder_modules << constant_name
end
version_finder_module.const_get(constant_name)
end
- # @return [ Array ]
- def self.version_finder_modules
- @version_finder_modules ||= version_finder_module.constants
- end
-
def self.create_versions_finders
versions_finders_configs.each do |slug, finders|
mod = maybe_create_module(slug)
diff --git a/wpscan.gemspec b/wpscan.gemspec
index 505edec3..a4cc4cfa 100644
--- a/wpscan.gemspec
+++ b/wpscan.gemspec
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
s.add_development_dependency 'bundler', '>= 1.6'
s.add_development_dependency 'coveralls', '~> 0.8.0'
- s.add_development_dependency 'memory_profiler', '~> 0.9.12'
+ s.add_development_dependency 'memory_profiler', '~> 0.9.13'
s.add_development_dependency 'rake', '~> 12.3'
s.add_development_dependency 'rspec', '~> 3.8.0'
s.add_development_dependency 'rspec-its', '~> 1.2.0'
From 231f5157bfd9458a03b640bb8a03ceb6969ab023 Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Fri, 22 Mar 2019 20:20:07 +0000
Subject: [PATCH 12/13] 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
From fa0582ce0b212b60cfbfb165209edb60060803b3 Mon Sep 17 00:00:00 2001
From: erwanlr
Date: Fri, 22 Mar 2019 20:35:22 +0000
Subject: [PATCH 13/13] Uses head or get method to enumerate config backups
---
app/finders/config_backups/known_filenames.rb | 13 +++++++++----
.../finders/config_backups/known_filenames_spec.rb | 11 ++++++-----
2 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/app/finders/config_backups/known_filenames.rb b/app/finders/config_backups/known_filenames.rb
index 6dc9f38f..f93503f5 100644
--- a/app/finders/config_backups/known_filenames.rb
+++ b/app/finders/config_backups/known_filenames.rb
@@ -3,7 +3,7 @@ module WPScan
module ConfigBackups
# Config Backup finder
class KnownFilenames < CMSScanner::Finders::Finder
- include CMSScanner::Finders::Finder::Enumerator
+ include Finders::Finder::Enumerator
# @param [ Hash ] opts
# @option opts [ String ] :list
@@ -14,15 +14,20 @@ module WPScan
found = []
enumerate(potential_urls(opts), opts) do |res|
- # Might need to improve that
- next unless res.body =~ /define/i && res.body !~ /<\s?html/i
-
found << WPScan::ConfigBackup.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
end
found
end
+ def valid_response?(res, _exclude_content = nil)
+ return unless res.code == 200
+
+ full_res = Browser.get(res.effective_url)
+
+ full_res.body =~ /define/i && full_res.body !~ /<\s?html/i
+ end
+
# @param [ Hash ] opts
# @option opts [ String ] :list Mandatory
#
diff --git a/spec/app/finders/config_backups/known_filenames_spec.rb b/spec/app/finders/config_backups/known_filenames_spec.rb
index ba8c97a7..c1abdaa0 100644
--- a/spec/app/finders/config_backups/known_filenames_spec.rb
+++ b/spec/app/finders/config_backups/known_filenames_spec.rb
@@ -8,10 +8,10 @@ describe WPScan::Finders::ConfigBackups::KnownFilenames 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
@@ -22,11 +22,12 @@ describe WPScan::Finders::ConfigBackups::KnownFilenames do
end
context 'when some files exist' do
- let(:files) { ['%23wp-config.php%23', 'wp-config.bak'] }
+ let(:found_files) { ['%23wp-config.php%23', 'wp-config.bak'] }
let(:config_backup) { File.read(fixtures.join('wp-config.php')) }
before do
- files.each do |file|
+ found_files.each do |file|
+ stub_request(:head, "#{url}#{file}").to_return(status: 200)
stub_request(:get, "#{url}#{file}").to_return(body: config_backup)
end
end
@@ -34,7 +35,7 @@ describe WPScan::Finders::ConfigBackups::KnownFilenames do
it 'returns the expected Array' do
expected = []
- files.each do |file|
+ found_files.each do |file|
url = "#{target.url}#{file}"
expected << WPScan::ConfigBackup.new(
url,