PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.9.0
Defender Security – Malware Scanner, Login Security & Firewall v5.9.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / component / security-tweaks / class-prevent-enum-users.php

class-prevent-enum-users.php in Defender Security – Malware Scanner, Login Security & Firewall 5.9.0, at src/component/security-tweaks/class-prevent-enum-users.php

295 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Prevents user enumeration on a WordPress site to enhance security by blocking various endpoints and methods that can
4 * be used to discover user information.
5 *
6 * @package WP_Defender\Component\Security_Tweaks
7 */
8
9 namespace WP_Defender\Component\Security_Tweaks;
10
11 use WP_Error;
12 use WP_Sitemaps_Provider;
13 use WP_Defender\Traits\Security_Tweaks_Option;
14
15 /**
16 * Prevent user enumeration.
17 */
18 class Prevent_Enum_Users extends Abstract_Security_Tweaks implements Security_Key_Const_Interface {
19
20 use Security_Tweaks_Option;
21
22 /**
23 * The slug identifier for this tweak.
24 *
25 * @var string
26 */
27 public string $slug = 'prevent-enum-users';
28
29 /**
30 * Status to check if the issue has been resolved.
31 *
32 * @var bool
33 */
34 public $resolved = false;
35
36 /**
37 * Enabled user enumerations list.
38 *
39 * @var array
40 */
41 private $enabled_user_enums = array();
42
43 /**
44 * Check whether the issue has been resolved or not.
45 *
46 * @return bool
47 */
48 public function check(): bool {
49 return $this->resolved;
50 }
51
52 /**
53 * Here is the code for processing.
54 *
55 * @return bool
56 */
57 public function process(): bool {
58 return true;
59 }
60
61 /**
62 * This is for un-do stuff that has been done in @process.
63 *
64 * @return bool
65 */
66 public function revert(): bool {
67 return true;
68 }
69
70 /**
71 * Shield up.
72 *
73 * @return void
74 */
75 public function shield_up() {
76 $this->resolved = true;
77
78 if ( is_admin() || defender_is_wp_cli() ) {
79 return;
80 }
81
82 add_filter( 'oembed_response_data', array( $this, 'oembed_author_filter' ), 99 );
83 add_filter( 'wp_sitemaps_users_pre_url_list', array( $this, 'block_user_url_sitemap' ), 99, 0 );
84 add_filter( 'wp_sitemaps_add_provider', array( $this, 'block_user_provider_sitemap' ), 99, 2 );
85 add_filter( 'rest_authentication_errors', array( $this, 'block_rest_api_user_endpoint' ) );
86
87 $query = defender_get_data_from_request( 'QUERY_STRING', 's' );
88 if ( ! is_string( $query ) || '' === $query ) {
89 return;
90 }
91
92 $this->maybe_block( $query );
93
94 add_filter( 'redirect_canonical', array( $this, 'maybe_block' ) );
95 }
96
97 /**
98 * Maybe block the request if it's trying to access the author page with query param.
99 *
100 * @param string $request Request of current page.
101 *
102 * @return string
103 */
104 public function maybe_block( string $request ) {
105 if ( ! current_user_can( 'edit_others_posts' ) ) {
106 $params = array();
107 wp_parse_str( strtolower( $request ), $params );
108
109 if (
110 isset( $params['author'] )
111 && is_scalar( $params['author'] )
112 && '' !== preg_replace( '/[^0-9,-]/', '', $params['author'] )
113 ) {
114 wp_die(
115 esc_html__( 'Sorry, you are not allowed to access this page', 'defender-security' ),
116 esc_html__( 'Forbidden', 'defender-security' ),
117 403
118 );
119 }
120 }
121
122 return $request;
123 }
124
125 /**
126 * Retrieve the tweak's label.
127 *
128 * @return string
129 */
130 public function get_label(): string {
131 return esc_html__( 'Prevent user enumeration', 'defender-security' );
132 }
133
134 /**
135 * Get the error reason.
136 *
137 * @return string
138 */
139 public function get_error_reason(): string {
140 return esc_html__( 'User enumeration is currently allowed.', 'defender-security' );
141 }
142
143 /**
144 * Return a summary data of this tweak.
145 *
146 * @return array
147 */
148 public function to_array(): array {
149 return array(
150 'slug' => $this->slug,
151 'title' => $this->get_label(),
152 'errorReason' => $this->get_error_reason(),
153 'successReason' => esc_html__( 'User enumeration is currently blocked, nice work!', 'defender-security' ),
154 'misc' => array(),
155 'bulk_description' => esc_html__(
156 'To brute force your login, hackers and bots can simply type the query string ?author=1, ?author=2 and so on, which will redirect the page to /author/username/ - bam, the bot now has your usernames to begin brute force attacks with. We can add a .htaccess file to your site to prevent the redirection.',
157 'defender-security'
158 ),
159 'bulk_title' => $this->get_label(),
160 );
161 }
162
163 /**
164 * Getter method of enabled_user_enums.
165 *
166 * @return array Return array of enabled enumeration options.
167 */
168 public function get_enabled_user_enums(): array {
169 $enabled_user_enums = $this->get_option( 'enabled_user_enums' );
170
171 $this->enabled_user_enums = is_null( $enabled_user_enums )
172 ? array()
173 : (array) $enabled_user_enums;
174
175 return $this->enabled_user_enums;
176 }
177
178 /**
179 * Setter method of enabled_user_enums.
180 *
181 * @param array $value Array of enabled user enumeration options.
182 *
183 * @return bool Return true if value updated, otherwise false.
184 */
185 public function set_enabled_user_enums( $value ): bool {
186 $this->enabled_user_enums = ! is_array( $value ) ? (array) $value : $value;
187
188 return $this->update_option(
189 'enabled_user_enums',
190 $this->enabled_user_enums
191 );
192 }
193
194 /**
195 * Filters the author detail from oEmbed response data.
196 *
197 * @param array $data The response data.
198 *
199 * @return array $data The response data.
200 */
201 public function oembed_author_filter( $data ) {
202 if (
203 is_array( $data ) &&
204 in_array(
205 'enum-oembed',
206 $this->get_enabled_user_enums(),
207 true
208 )
209 ) {
210 unset( $data['author_name'], $data['author_url'] );
211 }
212
213 return $data;
214 }
215
216 /**
217 * Blocks user url generation in sitemap.
218 * Consider the URL: http://example.com/wp-sitemap-users-1.xml will not return user URL XML.
219 *
220 * @return bool|null If option sitemap enumeration enabled then return false else null/void will return.
221 */
222 public function block_user_url_sitemap(): ?bool {
223 if (
224 in_array(
225 'enum-sitemap',
226 $this->get_enabled_user_enums(),
227 true
228 )
229 ) {
230 return false;
231 }
232
233 return null;
234 }
235
236 /**
237 * Blocks the user provider sitemap before it is added from wp-sitemap.xml.
238 *
239 * @param WP_Sitemaps_Provider $provider Instance of a WP_Sitemaps_Provider.
240 * @param string $name Name of the sitemap provider.
241 *
242 * @return bool|WP_Sitemaps_Provider If option sitemap enumeration enabled then return false else
243 * WP_Sitemaps_Provider instance.
244 */
245 public function block_user_provider_sitemap( $provider, string $name ) {
246 if (
247 'users' === $name &&
248 in_array(
249 'enum-sitemap',
250 $this->get_enabled_user_enums(),
251 true
252 )
253 ) {
254 return false;
255 }
256
257 return $provider;
258 }
259
260 /**
261 * Block user REST API endpoint for not logged-in access.
262 *
263 * @param WP_Error|null|true $errors WP_Error if authentication error, null if authentication
264 * method wasn't used, true if authentication succeeded.
265 *
266 * @return WP_Error|null|true $errors WP_Error if authentication error, null if authentication
267 * method wasn't used, true if authentication succeeded.
268 */
269 public function block_rest_api_user_endpoint( $errors ) {
270 $request_uri = (string) filter_input( INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL ) ?? '';
271 $rest_route = (string) filter_input( INPUT_GET, 'rest_route', FILTER_SANITIZE_URL ) ?? '';
272
273 $is_url_match = preg_match( '/users/i', $request_uri ) !== 0 ||
274 preg_match( '/users/i', $rest_route ) !== 0;
275
276 if (
277 in_array(
278 'enum-rest',
279 $this->get_enabled_user_enums(),
280 true
281 ) &&
282 $is_url_match &&
283 ! is_user_logged_in()
284 ) {
285 return new WP_Error(
286 'rest_cannot_access',
287 esc_html__( 'Only logged-in users can access the User endpoint REST API.', 'defender-security' ),
288 array( 'status' => rest_authorization_required_code() )
289 );
290 }
291
292 return $errors;
293 }
294 }
295