HELLO v3!!!

This commit is contained in:
Ryan Dewhurst
2018-09-26 21:12:01 +02:00
parent 28b9c15256
commit d268a86795
1871 changed files with 988118 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
module WPScan
module Finders
module Users
# Author Id Brute Forcing
class AuthorIdBruteForcing < CMSScanner::Finders::Finder
include CMSScanner::Finders::Finder::Enumerator
# @param [ Hash ] opts
# @option opts [ Range ] :range Mandatory
#
# @return [ Array<User> ]
def aggressive(opts = {})
found = []
found_by_msg = 'Author Id Brute Forcing - %s (Aggressive Detection)'
enumerate(target_urls(opts), opts) do |res, id|
username, found_by, confidence = potential_username(res)
next unless username
found << CMSScanner::User.new(
username,
id: id,
found_by: format(found_by_msg, found_by),
confidence: confidence
)
end
found
end
# @param [ Hash ] opts
# @option opts [ Range ] :range
#
# @return [ Hash ]
def target_urls(opts = {})
urls = {}
opts[:range].each do |id|
urls[target.uri.join("?author=#{id}").to_s] = id
end
urls
end
def create_progress_bar(opts = {})
super(opts.merge(title: ' Brute Forcing Author IDs -'))
end
def request_params
{ followlocation: true }
end
# @param [ Typhoeus::Response ] res
#
# @return [ Array<String, String, Integer>, nil ] username, found_by, confidence
def potential_username(res)
username = username_from_author_url(res.effective_url) || username_from_response(res)
return username, 'Author Pattern', 100 if username
username = display_name_from_body(res.body)
return username, 'Display Name', 50 if username
end
# @param [ String ] url
#
# @return [ String, nil ]
def username_from_author_url(url)
url[%r{/author/([^/\b]+)/?}i, 1]
end
# @param [ Typhoeus::Response ] res
#
# @return [ String, nil ] The username found
def username_from_response(res)
# Permalink enabled
target.in_scope_urls(res, '//link/@href|//a/@href') do |url|
username = username_from_author_url(url)
return username if username
end
# No permalink
res.body[/<body class="archive author author-([^\s]+)[ "]/i, 1]
end
# @param [ String ] body
#
# @return [ String, nil ]
def display_name_from_body(body)
page = Nokogiri::HTML.parse(body)
# WP >= 3.0
page.css('h1.page-title span').each do |node|
return node.text.to_s
end
# WP < 3.0
page.xpath('//link[@rel="alternate" and @type="application/rss+xml"]').each do |node|
title = node['title']
next unless title =~ /Posts by (.*) Feed\z/i
return Regexp.last_match[1] unless Regexp.last_match[1].empty?
end
nil
end
end
end
end
end

View File

@@ -0,0 +1,61 @@
module WPScan
module Finders
module Users
# Author Posts
class AuthorPosts < CMSScanner::Finders::Finder
# @param [ Hash ] opts
#
# @return [ Array<User> ]
def passive(opts = {})
found_by_msg = 'Author Posts - %s (Passive Detection)'
usernames(opts).reduce([]) do |a, e|
a << CMSScanner::User.new(
e[0],
found_by: format(found_by_msg, e[1]),
confidence: e[2]
)
end
end
# @param [ Hash ] opts
#
# @return [ Array<Array>> ]
def usernames(_opts = {})
found = potential_usernames(target.homepage_res)
return found unless found.empty?
target.homepage_res.html.css('header.entry-header a').each do |post_url_node|
url = post_url_node['href']
next if url.nil? || url.empty?
found += potential_usernames(Browser.get(url))
end
found.compact.uniq
end
# @param [ Typhoeus::Response ] res
#
# @return [ Array<Array> ]
def potential_usernames(res)
usernames = []
target.in_scope_urls(res, '//a/@href') do |url, node|
uri = Addressable::URI.parse(url)
if uri.path =~ %r{/author/([^/\b]+)/?\z}i
usernames << [Regexp.last_match[1], 'Author Pattern', 100]
elsif uri.query =~ /author=[0-9]+/
usernames << [node.text.to_s.strip, 'Display Name', 30]
end
end
usernames.uniq
end
end
end
end
end

View File

@@ -0,0 +1,45 @@
module WPScan
module Finders
module Users
# Login Error Messages
#
# Existing username:
# WP < 3.1 - Incorrect password.
# WP >= 3.1 - The password you entered for the username admin is incorrect.
# Non existent username: Invalid username.
#
class LoginErrorMessages < CMSScanner::Finders::Finder
# @param [ Hash ] opts
# @option opts [ String ] :list
#
# @return [ Array<User> ]
def aggressive(opts = {})
found = []
usernames(opts).each do |username|
res = target.do_login(username, SecureRandom.hex[0, 8])
error = res.html.css('div#login_error').text.strip
return found if error.empty? # Protection plugin / error disabled
next unless error =~ /The password you entered for the username|Incorrect Password/i
found << CMSScanner::User.new(username, found_by: found_by, confidence: 100)
end
found
end
# @return [ Array<String> ] List of usernames to check
def usernames(opts = {})
# usernames from the potential Users found
unames = opts[:found].map(&:username)
[*opts[:list]].each { |uname| unames << uname.chomp }
unames.uniq
end
end
end
end
end

View File

@@ -0,0 +1,49 @@
module WPScan
module Finders
module Users
# Since WP 4.4, the oembed API can disclose a user
# https://github.com/wpscanteam/wpscan/issues/1049
class OembedApi < CMSScanner::Finders::Finder
# @param [ Hash ] opts
#
# @return [ Array<User> ]
def passive(_opts = {})
# TODO: get the api_url from the Homepage and query it if present,
# then discard the aggressive check if same/similar URL
end
# @param [ Hash ] opts
#
# TODO: make this code pretty :x
#
# @return [ Array<User> ]
def aggressive(_opts = {})
found = []
found_by_msg = 'Oembed API - %s (Aggressive Detection)'
oembed_data = JSON.parse(Browser.get(api_url).body)
if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z}
details = [Regexp.last_match[1], 'Author URL', 90]
elsif oembed_data['author_name'] && !oembed_data['author_name'].empty?
details = [oembed_data['author_name'].delete(' '), 'Author Name', 70]
end
return unless details
found << CMSScanner::User.new(details[0],
found_by: format(found_by_msg, details[1]),
confidence: details[2],
interesting_entries: [api_url])
rescue JSON::ParserError
found
end
# @return [ String ] The URL of the API listing the Users
def api_url
@api_url ||= target.url("wp-json/oembed/1.0/embed?url=#{target.url}&format=json")
end
end
end
end
end

View File

@@ -0,0 +1,38 @@
module WPScan
module Finders
module Users
# Users disclosed from the dc:creator field in the RSS
# The names disclosed are display names, however depending on the configuration of the blog,
# they can be the same than usernames
class RSSGenerator < WPScan::Finders::WpVersion::RSSGenerator
def process_urls(urls, _opts = {})
found = []
urls.each do |url|
res = Browser.get_and_follow_location(url)
next unless res.code == 200 && res.body =~ /<dc\:creator>/i
potential_usernames = []
begin
res.xml.xpath('//item/dc:creator').each do |node|
potential_usernames << node.text.to_s unless node.text.to_s.length > 40
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)
end
break
end
found
end
end
end
end
end

View File

@@ -0,0 +1,35 @@
module WPScan
module Finders
module Users
# WP JSON API
#
# Since 4.7 - Need more investigation as it seems WP 4.7.1 reduces the exposure, see https://github.com/wpscanteam/wpscan/issues/1038)
#
class WpJsonApi < CMSScanner::Finders::Finder
# @param [ Hash ] opts
#
# @return [ Array<User> ]
def aggressive(_opts = {})
found = []
JSON.parse(Browser.get(api_url).body)&.each do |user|
found << CMSScanner::User.new(user['slug'],
id: user['id'],
found_by: found_by,
confidence: 100,
interesting_entries: [api_url])
end
found
rescue JSON::ParserError, TypeError
found
end
# @return [ String ] The URL of the API listing the Users
def api_url
@api_url ||= target.url('wp-json/wp/v2/users/')
end
end
end
end
end