PluginProbe
WP EXtra – One Click Optimize / 8.7.0
WP EXtra – One Click Optimize v8.7.0
8.7.1 8.6.8 8.7.0 trunk 5.9 8.0 8.5.0 8.5.4 8.5.5 8.6.0 8.6.1 8.6.2 8.6.3 8.6.5
wp-extra / src / Modules / Common / Security.php

Security.php in WP EXtra – One Click Optimize 8.7.0, at src/Modules/Common/Security.php

371 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPEXtra\Modules\Common;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 use WPEXtra\Settings;
9 use WPEXtra\Helper;
10 use WPEXtra\Base;
11
12 class Security extends Base {
13
14 public function __construct() {
15 parent::__construct();
16 }
17
18 protected $features = [
19 'disable_embeds',
20 'disable_xmlrpc',
21 'remove_jquery_migrate',
22 'remove_wp_version',
23 'clean_head_links',
24 'remove_wlwmanifest_link',
25 'remove_rsd_link',
26 'remove_shortlink',
27 'disable_rss_feeds',
28 'disable_self_pingbacks',
29 'block_user_enumeration',
30 'security_headers',
31 'themeplugin_edits',
32 'core_updates',
33 'http_request',
34 'disable_rest_api',
35 'remove_rest_api_links',
36 'disable_heartbeat',
37 'heartbeat_frequency',
38 'remove_blocks',
39 ];
40
41 public function clean_head_links() {
42 $this->remove_wlwmanifest_link();
43 $this->remove_rsd_link();
44 $this->remove_shortlink();
45 $this->disable_self_pingbacks();
46 }
47
48 public function disable_embeds() {
49 add_action('init', [$this, 'disable_embed'], 9999);
50 }
51
52 public function disable_embed() {
53 global $wp;
54 if (isset($wp->public_query_vars) && is_array($wp->public_query_vars)) {
55 $wp->public_query_vars = array_diff($wp->public_query_vars, ['embed']);
56 }
57 add_filter('embed_oembed_discover', '__return_false');
58 remove_action('wp_head', 'wp_oembed_add_discovery_links');
59 remove_action('wp_head', 'wp_oembed_add_host_js');
60 remove_filter('oembed_dataparse', 'wp_filter_oembed_result', 10);
61 remove_filter('pre_oembed_result', 'wp_filter_pre_oembed_result', 10);
62 add_filter('tiny_mce_plugins', [$this, 'disableEmbedsTinyMCE']);
63 add_filter('rewrite_rules_array', [$this, 'disableEmbedsRewrites']);
64 }
65
66 public function disableEmbedsTinyMCE($plugins) {
67 return is_array($plugins) ? array_diff($plugins, ['wpembed']) : $plugins;
68 }
69
70 public function disableEmbedsRewrites($rules) {
71 if (is_array($rules)) {
72 foreach ($rules as $rule => $rewrite) {
73 if (false !== strpos($rewrite, 'embed=true')) {
74 unset($rules[$rule]);
75 }
76 }
77 }
78 return $rules;
79 }
80
81 public function disable_xmlrpc() {
82 add_filter('xmlrpc_enabled', '__return_false');
83 add_filter('pings_open', '__return_false', 9999);
84 add_filter('pre_update_option_enable_xmlrpc', '__return_false');
85 add_filter('pre_option_enable_xmlrpc', '__return_zero');
86 add_filter('wp_headers', [$this, 'remove_xpingback']);
87 add_action('init', [$this, 'intercept_xmlrpc_header']);
88 }
89
90 public function remove_xpingback($headers) {
91 unset($headers['X-Pingback'], $headers['x-pingback']);
92 return $headers;
93 }
94
95 public function intercept_xmlrpc_header() {
96 if (!isset($_SERVER['SCRIPT_FILENAME'])) {
97 return;
98 }
99 if ('xmlrpc.php' !== basename($_SERVER['SCRIPT_FILENAME'])) {
100 return;
101 }
102 $header = 'HTTP/1.1 403 Forbidden';
103 header($header);
104 echo esc_html($header);
105 exit;
106 }
107
108 public function remove_jquery_migrate() {
109 add_filter('wp_default_scripts', [$this, 'jquery_migrate']);
110 }
111
112 public function jquery_migrate(&$scripts) {
113 if (!is_admin()) {
114 $scripts->remove('jquery');
115 $scripts->add('jquery', false, ['jquery-core']);
116 }
117 }
118
119 public function remove_wp_version() {
120 remove_action('wp_head', 'wp_generator');
121 add_filter('the_generator', '__return_empty_string');
122 }
123
124 public function remove_wlwmanifest_link() {
125 remove_action('wp_head', 'wlwmanifest_link');
126 }
127
128 public function remove_rsd_link() {
129 remove_action('wp_head', 'rsd_link');
130 }
131
132 public function remove_shortlink() {
133 remove_action('wp_head', 'wp_shortlink_wp_head');
134 remove_action('template_redirect', 'wp_shortlink_header', 11, 0);
135 }
136
137 public function disable_rss_feeds() {
138 add_action('template_redirect', [$this, 'rss_feed'], 1);
139 remove_action('wp_head', 'feed_links', 2);
140 remove_action('wp_head', 'feed_links_extra', 3);
141 }
142
143 public function rss_feed() {
144 if (!is_feed() || is_404()) {
145 return;
146 }
147 if (isset($_GET['feed'])) {
148 wp_safe_redirect(esc_url_raw(remove_query_arg('feed')), 301);
149 exit;
150 }
151 if (get_query_var('feed') !== 'old') {
152 set_query_var('feed', '');
153 }
154 redirect_canonical();
155 wp_die(sprintf(esc_html__("No feed available, please visit the <a href='%s'>homepage</a>!", 'wp-extra'), esc_url(home_url('/'))));
156 }
157
158 public function disable_self_pingbacks() {
159 add_action('pre_ping', [$this, 'self_pingbacks']);
160 }
161
162 public function self_pingbacks(&$links) {
163 $home = get_option('home');
164 foreach ($links as $l => $link) {
165 if (strpos($link, $home) === 0) {
166 unset($links[$l]);
167 }
168 }
169 }
170
171 public function block_user_enumeration() {
172 if (!is_admin()) {
173 // Block query string ?author=N for non-logged-in visitors
174 if (isset($_REQUEST['author']) && (is_numeric($_REQUEST['author']) || '' !== $_REQUEST['author'])) {
175 wp_safe_redirect(home_url(), 301);
176 exit;
177 }
178 // Hide REST API user list from unauthorized visitors
179 add_filter('rest_endpoints', [$this, 'filter_rest_user_endpoints']);
180 }
181 }
182
183 public function filter_rest_user_endpoints($endpoints) {
184 if (!is_user_logged_in()) {
185 if (isset($endpoints['/wp/v2/users'])) {
186 unset($endpoints['/wp/v2/users']);
187 }
188 if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) {
189 unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
190 }
191 }
192 return $endpoints;
193 }
194
195 public function security_headers() {
196 add_filter('wp_headers', [$this, 'send_security_headers']);
197 }
198
199 public function send_security_headers($headers) {
200 if (!is_admin()) {
201 $headers['X-Frame-Options'] = 'SAMEORIGIN';
202 $headers['X-Content-Type-Options'] = 'nosniff';
203 $headers['Referrer-Policy'] = 'strict-origin-when-cross-origin';
204 $headers['Permissions-Policy'] = 'camera=(), microphone=(), geolocation=()';
205 }
206 return $headers;
207 }
208
209
210 public function themeplugin_edits() {
211 add_filter('map_meta_cap', [$this, 'disable_file_editor_caps'], 10, 2);
212 if (!defined('DISALLOW_FILE_EDIT')) {
213 define('DISALLOW_FILE_EDIT', true);
214 }
215 }
216
217 public function disable_file_editor_caps($caps, $cap) {
218 if (in_array($cap, ['edit_themes', 'edit_plugins', 'edit_files'], true)) {
219 $caps[] = 'do_not_allow';
220 }
221 return $caps;
222 }
223
224 public function core_updates() {
225 add_filter('auto_update_core', '__return_false');
226 add_filter('automatic_updater_disabled', '__return_true');
227 }
228
229 public function http_request() {
230 add_filter('pre_http_request', [$this, 'pass_reject_request'], 10, 3);
231 }
232
233 public function pass_reject_request($preempt, $parsed_args, $url) {
234 $value = Helper::get_option('http_request', '');
235 $blocked_domains = array_filter(array_map('trim', explode("\n", $value)));
236 foreach ($blocked_domains as $domain) {
237 if (strpos($url, $domain) !== false) {
238 return new \WP_Error('http_request_block', esc_html__('Blocked by WP EXtra', 'wp-extra'));
239 }
240 }
241 return $preempt;
242 }
243
244 public function disable_rest_api() {
245 add_filter('rest_authentication_errors', [$this, 'restAuthenticationErrors'], 20);
246 }
247
248 public function restAuthenticationErrors($result) {
249 if (!empty($result)) {
250 return $result;
251 }
252
253 $rest_route = isset($GLOBALS['wp']->query_vars['rest_route']) ? $GLOBALS['wp']->query_vars['rest_route'] : '';
254 $exceptions = apply_filters('wpex_rest_api_exceptions', [
255 'contact-form-7',
256 'wordfence',
257 'elementor',
258 'woocommerce',
259 'fluentform',
260 'wpforms'
261 ]);
262
263 foreach ($exceptions as $exception) {
264 if (!empty($rest_route) && strpos($rest_route, $exception) !== false) {
265 return $result;
266 }
267 }
268
269 $disabled = false;
270 $disableOption = Helper::get_option('disable_rest_api', '');
271 $disableOptions = is_array($disableOption) ? $disableOption : [$disableOption];
272
273 if (in_array('all', $disableOptions, true)) {
274 $disabled = true;
275 } elseif (in_array('non_admins', $disableOptions, true) && !current_user_can('manage_options')) {
276 $disabled = true;
277 } elseif (in_array('logged_out', $disableOptions, true) && !is_user_logged_in()) {
278 $disabled = true;
279 }
280
281 if ($disabled) {
282 return new \WP_Error('rest_authentication_error', __('Sorry, you do not have permission to make REST API requests.', 'wp-extra'), ['status' => 401]);
283 }
284 return $result;
285 }
286
287 public function remove_rest_api_links() {
288 remove_action('xmlrpc_rsd_apis', 'rest_output_rsd');
289 remove_action('wp_head', 'rest_output_link_wp_head');
290 remove_action('template_redirect', 'rest_output_link_header', 11, 0);
291 }
292
293 public function disable_heartbeat() {
294 add_action('init', [$this, 'disableHeartbeat'], 1);
295 }
296
297 public function disableHeartbeat() {
298 if (is_admin()) {
299 global $pagenow;
300 if (!empty($pagenow)) {
301 if ($pagenow === 'admin.php' && !empty($_GET['page'])) {
302 $exceptions = [
303 'gf_edit_forms',
304 'gf_entries',
305 'gf_settings'
306 ];
307 if (in_array($_GET['page'], $exceptions, true)) {
308 return;
309 }
310 }
311 if ($pagenow === 'site-health.php') {
312 return;
313 }
314 }
315 }
316 $setting = Helper::get_option('disable_heartbeat');
317 if ($setting) {
318 if ($setting === 'everywhere') {
319 $this->replaceHearbeat();
320 } elseif ($setting === 'allow_posts') {
321 global $pagenow;
322 if ($pagenow !== 'post.php' && $pagenow !== 'post-new.php') {
323 $this->replaceHearbeat();
324 }
325 }
326 }
327 }
328
329 private function replaceHearbeat() {
330 wp_deregister_script('heartbeat');
331 if (is_admin() && Helper::get_option('disable_heartbeat')) {
332 wp_register_script('heartbeat', plugins_url('/assets/js/heartbeat.min.js', WPEX_FILE));
333 wp_enqueue_script('heartbeat', plugins_url('/assets/js/heartbeat.min.js', WPEX_FILE));
334 }
335 }
336
337 public function heartbeat_frequency() {
338 add_filter('heartbeat_settings', [$this, 'heartbeatFrequency']);
339 }
340
341 public function heartbeatFrequency($settings) {
342 $freq = Helper::get_option('heartbeat_frequency');
343 if ($freq) {
344 $settings['interval'] = intval($freq);
345 }
346 return $settings;
347 }
348
349 public function remove_blocks() {
350 add_filter('allowed_block_types_all', [$this, 'remove_default_blocks']);
351 add_filter('allowed_block_types', [$this, 'remove_default_blocks']);
352 }
353
354 public function remove_default_blocks($allowed_blocks) {
355 if (!class_exists('\WP_Block_Type_Registry')) {
356 return $allowed_blocks;
357 }
358 $registered_blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered();
359 $filtered_blocks = [];
360
361 foreach ($registered_blocks as $block) {
362 if (strpos($block->name, 'core/') === false) {
363 if (!class_exists('WooCommerce') || strpos($block->name, 'woocommerce/') === false) {
364 $filtered_blocks[] = $block->name;
365 }
366 }
367 }
368 return $filtered_blocks;
369 }
370
371 }