mirror of
https://gitlab.com/ArkHost/WP-Security-Pack.git
synced 2026-09-19 17:37:30 +02:00
v1.0
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
/**
|
||||
* Security hardening for WP Security Pack.
|
||||
*
|
||||
* @package WP_Security_Pack
|
||||
*/
|
||||
|
||||
// Prevent direct access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hardening class for security enhancements.
|
||||
*/
|
||||
class WPSP_Hardening {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Disable XML-RPC.
|
||||
if ( WP_Security_Pack::get_setting( 'disable_xmlrpc', true ) ) {
|
||||
add_filter( 'xmlrpc_enabled', '__return_false' );
|
||||
add_filter( 'wp_headers', array( $this, 'remove_xmlrpc_headers' ) );
|
||||
add_action( 'wp', array( $this, 'block_xmlrpc_requests' ), 1 );
|
||||
}
|
||||
|
||||
// Disable file editing.
|
||||
if ( WP_Security_Pack::get_setting( 'disable_file_editing', true ) ) {
|
||||
$this->disable_file_editing();
|
||||
}
|
||||
|
||||
// Remove WordPress version.
|
||||
if ( WP_Security_Pack::get_setting( 'remove_wp_version', true ) ) {
|
||||
add_filter( 'the_generator', '__return_empty_string' );
|
||||
remove_action( 'wp_head', 'wp_generator' );
|
||||
add_filter( 'style_loader_src', array( $this, 'remove_version_strings' ), 10, 2 );
|
||||
add_filter( 'script_loader_src', array( $this, 'remove_version_strings' ), 10, 2 );
|
||||
}
|
||||
|
||||
// Add security headers.
|
||||
if ( WP_Security_Pack::get_setting( 'add_security_headers', true ) ) {
|
||||
add_action( 'send_headers', array( $this, 'add_security_headers' ) );
|
||||
}
|
||||
|
||||
// Restrict REST API.
|
||||
if ( WP_Security_Pack::get_setting( 'restrict_rest_api', true ) ) {
|
||||
add_filter( 'rest_authentication_errors', array( $this, 'restrict_rest_api' ) );
|
||||
}
|
||||
|
||||
// Disable application passwords for non-admins.
|
||||
if ( WP_Security_Pack::get_setting( 'disable_application_passwords', false ) ) {
|
||||
add_filter( 'wp_is_application_passwords_available', '__return_false' );
|
||||
}
|
||||
|
||||
// Remove unnecessary headers.
|
||||
add_action( 'init', array( $this, 'remove_unnecessary_headers' ) );
|
||||
|
||||
// Disable user enumeration.
|
||||
if ( WP_Security_Pack::get_setting( 'disable_user_enumeration', true ) ) {
|
||||
add_action( 'init', array( $this, 'block_author_scanning' ) );
|
||||
add_filter( 'rest_endpoints', array( $this, 'restrict_users_endpoint' ) );
|
||||
add_filter( 'oembed_response_data', array( $this, 'remove_author_from_oembed' ) );
|
||||
}
|
||||
|
||||
// Disable pingbacks/trackbacks.
|
||||
if ( WP_Security_Pack::get_setting( 'disable_pingbacks', true ) ) {
|
||||
add_filter( 'xmlrpc_methods', array( $this, 'disable_pingback_methods' ) );
|
||||
add_filter( 'wp_headers', array( $this, 'remove_pingback_header' ) );
|
||||
add_filter( 'pings_open', '__return_false', 20, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove XML-RPC related headers.
|
||||
*
|
||||
* @param array $headers HTTP headers.
|
||||
* @return array
|
||||
*/
|
||||
public function remove_xmlrpc_headers( $headers ) {
|
||||
unset( $headers['X-Pingback'] );
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block direct XML-RPC requests.
|
||||
*/
|
||||
public function block_xmlrpc_requests() {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
|
||||
|
||||
if ( strpos( $request_uri, 'xmlrpc.php' ) !== false ) {
|
||||
status_header( 403 );
|
||||
exit( 'XML-RPC is disabled.' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable file editing in dashboard.
|
||||
*/
|
||||
private function disable_file_editing() {
|
||||
if ( ! defined( 'DISALLOW_FILE_EDIT' ) ) {
|
||||
define( 'DISALLOW_FILE_EDIT', true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove version strings from scripts and styles.
|
||||
*
|
||||
* @param string $src Source URL.
|
||||
* @param string $handle Handle name.
|
||||
* @return string
|
||||
*/
|
||||
public function remove_version_strings( $src, $handle ) {
|
||||
if ( strpos( $src, 'ver=' ) !== false ) {
|
||||
$src = remove_query_arg( 'ver', $src );
|
||||
}
|
||||
return $src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add security headers.
|
||||
*/
|
||||
public function add_security_headers() {
|
||||
// Don't add headers for admin pages if user is logged in.
|
||||
if ( is_admin() && is_user_logged_in() ) {
|
||||
// Still add some basic headers.
|
||||
header( 'X-Content-Type-Options: nosniff' );
|
||||
return;
|
||||
}
|
||||
|
||||
// Get header settings.
|
||||
$headers = WP_Security_Pack::get_setting( 'security_headers', $this->get_default_headers() );
|
||||
|
||||
// X-Content-Type-Options.
|
||||
if ( ! empty( $headers['x_content_type_options'] ) ) {
|
||||
header( 'X-Content-Type-Options: ' . $headers['x_content_type_options'] );
|
||||
}
|
||||
|
||||
// X-Frame-Options.
|
||||
if ( ! empty( $headers['x_frame_options'] ) ) {
|
||||
header( 'X-Frame-Options: ' . $headers['x_frame_options'] );
|
||||
}
|
||||
|
||||
// X-XSS-Protection.
|
||||
if ( ! empty( $headers['x_xss_protection'] ) ) {
|
||||
header( 'X-XSS-Protection: ' . $headers['x_xss_protection'] );
|
||||
}
|
||||
|
||||
// Referrer-Policy.
|
||||
if ( ! empty( $headers['referrer_policy'] ) ) {
|
||||
header( 'Referrer-Policy: ' . $headers['referrer_policy'] );
|
||||
}
|
||||
|
||||
// Permissions-Policy.
|
||||
if ( ! empty( $headers['permissions_policy'] ) ) {
|
||||
header( 'Permissions-Policy: ' . $headers['permissions_policy'] );
|
||||
}
|
||||
|
||||
// Content-Security-Policy.
|
||||
if ( ! empty( $headers['content_security_policy'] ) ) {
|
||||
header( 'Content-Security-Policy: ' . $headers['content_security_policy'] );
|
||||
}
|
||||
|
||||
// Strict-Transport-Security (HSTS).
|
||||
if ( ! empty( $headers['strict_transport_security'] ) && is_ssl() ) {
|
||||
header( 'Strict-Transport-Security: ' . $headers['strict_transport_security'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default security headers.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_default_headers() {
|
||||
return array(
|
||||
'x_content_type_options' => 'nosniff',
|
||||
'x_frame_options' => 'SAMEORIGIN',
|
||||
'x_xss_protection' => '1; mode=block',
|
||||
'referrer_policy' => 'strict-origin-when-cross-origin',
|
||||
'permissions_policy' => 'geolocation=(), microphone=(), camera=()',
|
||||
'content_security_policy' => '',
|
||||
'strict_transport_security' => 'max-age=31536000; includeSubDomains',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict REST API to authenticated users.
|
||||
*
|
||||
* @param WP_Error|null|bool $result Authentication result.
|
||||
* @return WP_Error|null|bool
|
||||
*/
|
||||
public function restrict_rest_api( $result ) {
|
||||
// If there's already an error, return it.
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Allow if user is logged in.
|
||||
if ( is_user_logged_in() ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Get allowed REST routes.
|
||||
$allowed_routes = WP_Security_Pack::get_setting( 'rest_api_allowed_routes', array() );
|
||||
|
||||
// Always allow some essential routes.
|
||||
$essential_routes = array(
|
||||
'/wp/v2/oembed',
|
||||
'/wp-site-health',
|
||||
);
|
||||
|
||||
$allowed_routes = array_merge( $allowed_routes, $essential_routes );
|
||||
|
||||
// Get current route.
|
||||
$current_route = $GLOBALS['wp']->query_vars['rest_route'] ?? '';
|
||||
|
||||
// Check if current route is allowed.
|
||||
foreach ( $allowed_routes as $route ) {
|
||||
if ( strpos( $current_route, $route ) === 0 ) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Block unauthenticated access.
|
||||
return new WP_Error(
|
||||
'rest_not_logged_in',
|
||||
__( 'You must be authenticated to access this endpoint.', 'wp-security-pack' ),
|
||||
array( 'status' => 401 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecessary headers.
|
||||
*/
|
||||
public function remove_unnecessary_headers() {
|
||||
// Remove Really Simple Discovery link.
|
||||
remove_action( 'wp_head', 'rsd_link' );
|
||||
|
||||
// Remove Windows Live Writer manifest link.
|
||||
remove_action( 'wp_head', 'wlwmanifest_link' );
|
||||
|
||||
// Remove shortlink.
|
||||
remove_action( 'wp_head', 'wp_shortlink_wp_head' );
|
||||
|
||||
// Remove feed links.
|
||||
if ( WP_Security_Pack::get_setting( 'remove_feed_links', false ) ) {
|
||||
remove_action( 'wp_head', 'feed_links', 2 );
|
||||
remove_action( 'wp_head', 'feed_links_extra', 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Block author scanning via ?author=N URLs.
|
||||
*/
|
||||
public function block_author_scanning() {
|
||||
if ( is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Block ?author=N requests for non-logged-in users.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! is_user_logged_in() && isset( $_GET['author'] ) ) {
|
||||
wp_safe_redirect( home_url(), 301 );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict REST API users endpoint to authenticated users.
|
||||
*
|
||||
* @param array $endpoints REST API endpoints.
|
||||
* @return array
|
||||
*/
|
||||
public function restrict_users_endpoint( $endpoints ) {
|
||||
if ( is_user_logged_in() ) {
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
// Remove users endpoint for unauthenticated requests.
|
||||
if ( isset( $endpoints['/wp/v2/users'] ) ) {
|
||||
unset( $endpoints['/wp/v2/users'] );
|
||||
}
|
||||
if ( isset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ) ) {
|
||||
unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
|
||||
}
|
||||
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove author information from oEmbed responses.
|
||||
*
|
||||
* @param array $data oEmbed response data.
|
||||
* @return array
|
||||
*/
|
||||
public function remove_author_from_oembed( $data ) {
|
||||
if ( isset( $data['author_name'] ) ) {
|
||||
unset( $data['author_name'] );
|
||||
}
|
||||
if ( isset( $data['author_url'] ) ) {
|
||||
unset( $data['author_url'] );
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable pingback XML-RPC methods.
|
||||
*
|
||||
* @param array $methods XML-RPC methods.
|
||||
* @return array
|
||||
*/
|
||||
public function disable_pingback_methods( $methods ) {
|
||||
unset( $methods['pingback.ping'] );
|
||||
unset( $methods['pingback.extensions.getPingbacks'] );
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove X-Pingback header.
|
||||
*
|
||||
* @param array $headers HTTP headers.
|
||||
* @return array
|
||||
*/
|
||||
public function remove_pingback_header( $headers ) {
|
||||
unset( $headers['X-Pingback'] );
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user