Improves detection of WP Version, Plugins etc by checking 404

This commit is contained in:
erwanlr
2019-10-31 19:56:05 +00:00
parent 85aa9f61cd
commit 6b5e016770
44 changed files with 456 additions and 146 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -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],

View File

@@ -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) <<

View File

@@ -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/<slug>/
class UrlsIn404Page < UrlsInHomepage
# @return [ Typhoeus::Response ]
def page_res
@page_res ||= target.error_404_res
end
end
end
end
end

View File

@@ -4,10 +4,9 @@ module WPScan
module Finders
module Plugins
# URLs In Homepage Finder
# Typically, the items detected from URLs like
# /wp-content/plugins/<slug>/
# Typically, the items detected from URLs like /wp-content/plugins/<slug>/
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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,3 +1,3 @@
# frozen_string_literal: true
require_relative 'wp_items/urls_in_homepage'
require_relative 'wp_items/urls_in_page'

View File

@@ -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?

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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','(?<c_value>\d+)',\d+\);/i

View File

@@ -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

View File

@@ -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

View File

@@ -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'
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }

View File

@@ -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

View File

@@ -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

View File

@@ -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 }

View File

@@ -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

View File

@@ -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/',

View File

@@ -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

View File

@@ -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']

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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'))