Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wpscanteam
GitHub Repository: wpscanteam/wpscan
Path: blob/master/app/finders/users/author_id_brute_forcing.rb
485 views
1
# frozen_string_literal: true
2
3
module WPScan
4
module Finders
5
module Users
6
# Author Id Brute Forcing
7
class AuthorIdBruteForcing < CMSScanner::Finders::Finder
8
include CMSScanner::Finders::Finder::Enumerator
9
10
# @return [ Array<Integer> ]
11
def valid_response_codes
12
@valid_response_codes ||= [200, 301, 302]
13
end
14
15
# @param [ Hash ] opts
16
# @option opts [ Range ] :range Mandatory
17
#
18
# @return [ Array<User> ]
19
def aggressive(opts = {})
20
found = []
21
found_by_msg = 'Author Id Brute Forcing - %s (Aggressive Detection)'
22
23
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, id|
24
username, found_by, confidence = potential_username(res)
25
26
next unless username
27
28
found << Model::User.new(
29
username,
30
id: id,
31
found_by: format(found_by_msg, found_by),
32
confidence: confidence
33
)
34
end
35
36
found
37
end
38
39
# @param [ Hash ] opts
40
# @option opts [ Range ] :range
41
#
42
# @return [ Hash ]
43
def target_urls(opts = {})
44
urls = {}
45
46
opts[:range].each do |id|
47
urls[target.uri.join("?author=#{id}").to_s] = id
48
end
49
50
urls
51
end
52
53
def create_progress_bar(opts = {})
54
super(opts.merge(title: ' Brute Forcing Author IDs -'))
55
end
56
57
def full_request_params
58
{ followlocation: true }
59
end
60
61
# @param [ Typhoeus::Response ] res
62
#
63
# @return [ Array<String, String, Integer>, nil ] username, found_by, confidence
64
def potential_username(res)
65
username = username_from_author_url(res.effective_url) || username_from_response(res)
66
67
return username, 'Author Pattern', 100 if username
68
69
username = display_name_from_body(res.body)
70
71
return username, 'Display Name', 50 if username
72
end
73
74
# @param [ String, Addressable::URI ] uri
75
#
76
# @return [ String, nil ]
77
def username_from_author_url(uri)
78
uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
79
80
uri.path[%r{/author/([^/\b]+)/?}i, 1]
81
end
82
83
# @param [ Typhoeus::Response ] res
84
#
85
# @return [ String, nil ] The username found
86
def username_from_response(res)
87
# Permalink enabled
88
target.in_scope_uris(res, '//@href[contains(., "author/")]') do |uri|
89
username = username_from_author_url(uri)
90
return username if username
91
end
92
93
# No permalink, TODO Maybe use xpath to extract the classes ?
94
res.body[/<body class="archive author author-([^\s]+)[ "]/i, 1]
95
end
96
97
# @param [ String ] body
98
#
99
# @return [ String, nil ]
100
def display_name_from_body(body)
101
page = Nokogiri::HTML.parse(body)
102
103
# WP >= 3.0
104
page.css('h1.page-title span').each do |node|
105
text = node.text.to_s.strip
106
107
return text unless text.empty?
108
end
109
110
# WP < 3.0
111
page.xpath('//link[@rel="alternate" and @type="application/rss+xml"]').each do |node|
112
title = node['title']
113
114
next unless title =~ /Posts by (.*) Feed\z/i
115
116
return Regexp.last_match[1] unless Regexp.last_match[1].empty?
117
end
118
nil
119
end
120
end
121
end
122
end
123
end
124
125