Adds more improvements re passive scanning when there are a lot of urls

This commit is contained in:
erwanlr
2020-02-13 15:36:42 +00:00
parent 7d2b8a2a8b
commit 72d699b39a
18 changed files with 214 additions and 38 deletions

View File

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

View File

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

View File

@@ -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[/<body class="archive author author-([^\s]+)[ "]/i, 1]
end

View File

@@ -45,7 +45,7 @@ module WPScan
def potential_usernames(res)
usernames = []
target.in_scope_uris(res, '//a/@href') do |uri, node|
target.in_scope_uris(res, '//a/@href[contains(., "author")]') do |uri, node|
if uri.path =~ %r{/author/([^/\b]+)/?\z}i
usernames << [Regexp.last_match[1], 'Author Pattern', 100]
elsif /author=[0-9]+/.match?(uri.query)

View File

@@ -8,11 +8,15 @@ module WPScan
# @param [ String ] type plugins / themes
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
#
# @return [Array<String> ] The plugins/themes detected in the href, src attributes of the homepage
# @return [ Array<String> ] 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

View File

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

View File

@@ -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| "<a href='#{url}#{i}.html'>Some Link</a><img src='#{url}img-#{i}.png'/>" }.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

View File

@@ -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| "<a href='#{url}#{i}.html'>Some Link</a>" }.join("\n")
body << '<a href="https://wp.lab/author/test/">Link</a>'
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

View File

@@ -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| "<a href='#{url}#{i}.html'>Some Link</a>" }.join("\n")
body << "<a href='#{url}author/admin/'>Other Link</a>"
body << "<a href='#{url}?author=2'>user display name</a>"
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

View File

@@ -1,4 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
!.gitignore

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en-US" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<link rel="profile" href="http://gmpg.org/xfn/11">
<link rel="pingback" href="http://ex.lo/xmlrpc.php">
<script>(function(){document.documentElement.className='js'})();</script>
<title>WP 5.3.2 | Just another WordPress site</title>
<meta name='robots' content='noindex,follow' />
<link rel="alternate" type="application/rss+xml" title="WP 5.3.2 &raquo; Feed" href="http://ex.lo/feed/" />
<link rel="alternate" type="application/rss+xml" title="WP 5.3.2 &raquo; Comments Feed" href="http://ex.lo/comments/feed/" />
<link rel='stylesheet' id='twentyfifteen-style-css' href='http://g.com/wp-content/themes/twentyfifteen/style.css?ver=5.3.2' type='text/css' media='all' />
<link rel='stylesheet' id='wbb-css' href='https://ex.lo/wp-content/mu-plugins/bb-mods/css/style.css' type='text/css' media='all' />

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en-US" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<link rel="profile" href="http://gmpg.org/xfn/11">
<link rel="pingback" href="http://ex.lo/xmlrpc.php">
<script>(function(){document.documentElement.className='js'})();</script>
<title>WP 5.3.2 | Just another WordPress site</title>
<meta name='robots' content='noindex,follow' />
<link rel="alternate" type="application/rss+xml" title="WP 5.3.2 &raquo; Feed" href="http://ex.lo/feed/" />
<link rel="alternate" type="application/rss+xml" title="WP 5.3.2 &raquo; Comments Feed" href="http://ex.lo/comments/feed/" />
<link rel='stylesheet' id='twentyfifteen-style-css' href='http://g.com/wp-content/themes/twentyfifteen/style.css?ver=5.3.2' type='text/css' media='all' />
<script type='text/javascript' src='https://ex.lo/wp-content/mu-plugins/library/js/jquery-core.js'></script>

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en-US" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<link rel="profile" href="http://gmpg.org/xfn/11">
<link rel="pingback" href="http://ex.lo/xmlrpc.php">
<script>(function(){document.documentElement.className='js'})();</script>
<title>WP 5.3.2 | Just another WordPress site</title>
<meta name='robots' content='noindex,follow' />
<link rel="alternate" type="application/rss+xml" title="WP 5.3.2 &raquo; Feed" href="http://ex.lo/feed/" />
<link rel="alternate" type="application/rss+xml" title="WP 5.3.2 &raquo; Comments Feed" href="http://ex.lo/comments/feed/" />
<link rel='stylesheet' id='twentyfifteen-style-css' href='http://g.com/wp-content/themes/twentyfifteen/style.css?ver=5.3.2' type='text/css' media='all' />

View File

@@ -15,8 +15,12 @@
<meta name='robots' content='noindex,follow' />
<link rel="alternate" type="application/rss+xml" title="WP 4.1.1 &raquo; Feed" href="http://wp.lab/wordpress-4.1.1/feed/" />
<link rel="alternate" type="application/rss+xml" title="WP 4.1.1 &raquo; Comments Feed" href="http://wp.lab/wordpress-4.1.1/comments/feed/" />
<link rel="alternate" type="application/rss+xml" title="WP 4.1.1 &raquo; Comments Feed" href="http://wp.lab/wordpress-4.1.1/comments
/feed/" />
<a href="https://wp.lab/?s=/author/should-not-be-detected/">Link</a>
<link rel="alternate" type="application/rss+xml" title="WP 4.1.1 &raquo; Posts by admin display_name Feed" href="http://wp.lab/wordpress-4.1.1/author/admin/feed/" />
<link rel='stylesheet' id='twentyfifteen-fonts-css' href='//fonts.googleapis.com/css?family=Noto+Sans%3A400italic%2C700italic%2C400%2C700%7CNoto+Serif%3A400italic%2C700italic%2C400%2C700%7CInconsolata%3A400%2C700&#038;subset=latin%2Clatin-ext' type='text/css' media='all' />
<link rel='stylesheet' id='genericons-css' href='http://wp.lab/wordpress-4.1.1/wp-content/themes/twentyfifteen/genericons/genericons.css?ver=3.2' type='text/css' media='all' />
<link rel='stylesheet' id='twentyfifteen-style-css' href='http://wp.lab/wordpress-4.1.1/wp-content/themes/twentyfifteen/style.css?ver=4.1.1' type='text/css' media='all' />

View File

@@ -215,10 +215,6 @@ div.break_footer
document.adoffset = 0;
document.adPopupFile = '/cnn_adspaces/adsPopup2.html';
</script>
<script type="text/javascript" src="https://s2.wp.com/wp-content/themes/vip/lab/js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="https://s2.wp.com/wp-content/themes/vip/lab/js/jquery.vticker-min.js"></script>
<script type="text/javascript" src="https://s2.wp.com/wp-content/themes/vip/lab/js/adspaces.js"></script>
<script type="text/javascript" src="https://s2.wp.com/wp-content/themes/vip/lab/js/nbaOmEvent.js"></script>
<script type="text/javascript" src="http://i.cdn.turner.com/ads/adfuel/ais/nba-ais.js"></script>
<script language="JavaScript">

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head profile="http://gmpg.org/xfn/11">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>WP Lab</title>
<script type="text/javascript" src="https://s2.wp.com/wp-content/themes/vip/lab/js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="https://s2.wp.com/wp-content/themes/vip/lab/js/jquery.vticker-min.js"></script>
<script type="text/javascript" src="https://s2.wp.com/wp-content/themes/vip/lab/js/adspaces.js"></script>
<script type="text/javascript" src="https://s2.wp.com/wp-content/themes/vip/lab/js/nbaOmEvent.js"></script>

View File

@@ -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| "<a href='#{url}#{i}.html'>Link</a><img src='#{url}img-#{i}.gif'/>" }.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

View File

@@ -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| "<a href='#{url}#{i}.html'>Some Link</a><img src='#{url}img-#{i}.png'/>" }.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