PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.8.1
Defender Security – Malware Scanner, Login Security & Firewall v5.8.1
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.8.1, at src/component/security-tweaks/class-prevent-enum-users.php

293 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|void If option sitemap enumeration enabled then return false else null/void will return.
221 */
222 public function block_user_url_sitemap() {
223 if (
224 in_array(
225 'enum-sitemap',
226 $this->get_enabled_user_enums(),
227 true
228 )
229 ) {
230 return false;
231 }
232 }
233
234 /**
235 * Blocks the user provider sitemap before it is added from wp-sitemap.xml.
236 *
237 * @param WP_Sitemaps_Provider $provider Instance of a WP_Sitemaps_Provider.
238 * @param string $name Name of the sitemap provider.
239 *
240 * @return bool|WP_Sitemaps_Provider If option sitemap enumeration enabled then return false else
241 * WP_Sitemaps_Provider instance.
242 */
243 public function block_user_provider_sitemap( $provider, string $name ) {
244 if (
245 'users' === $name &&
246 in_array(
247 'enum-sitemap',
248 $this->get_enabled_user_enums(),
249 true
250 )
251 ) {
252 return false;
253 }
254
255 return $provider;
256 }
257
258 /**
259 * Block user REST API endpoint for not logged-in access.
260 *
261 * @param WP_Error|null|true $errors WP_Error if authentication error, null if authentication
262 * method wasn't used, true if authentication succeeded.
263 *
264 * @return WP_Error|null|true $errors WP_Error if authentication error, null if authentication
265 * method wasn't used, true if authentication succeeded.
266 */
267 public function block_rest_api_user_endpoint( $errors ) {
268 $request_uri = (string) filter_input( INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL ) ?? '';
269 $rest_route = (string) filter_input( INPUT_GET, 'rest_route', FILTER_SANITIZE_URL ) ?? '';
270
271 $is_url_match = preg_match( '/users/i', $request_uri ) !== 0 ||
272 preg_match( '/users/i', $rest_route ) !== 0;
273
274 if (
275 in_array(
276 'enum-rest',
277 $this->get_enabled_user_enums(),
278 true
279 ) &&
280 $is_url_match &&
281 ! is_user_logged_in()
282 ) {
283 return new WP_Error(
284 'rest_cannot_access',
285 esc_html__( 'Only logged-in users can access the User endpoint REST API.', 'defender-security' ),
286 array( 'status' => rest_authorization_required_code() )
287 );
288 }
289
290 return $errors;
291 }
292 }
293