This commit is contained in:
Yuri Karamian
2026-02-04 20:30:32 +01:00
parent 291fd61b67
commit 6b719f4fac
44 changed files with 4974 additions and 4948 deletions
@@ -0,0 +1,331 @@
<?php
/**
* Security hardening for Security Pack.
*
* @package ArkHost_Security_Pack
*/
// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Hardening class for security enhancements.
*/
class ARKSP_Hardening {
/**
* Constructor.
*/
public function __construct() {
// Disable XML-RPC.
if ( ARKSP_Plugin::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 ( ARKSP_Plugin::get_setting( 'disable_file_editing', true ) ) {
$this->disable_file_editing();
}
// Remove WordPress version.
if ( ARKSP_Plugin::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 ( ARKSP_Plugin::get_setting( 'add_security_headers', true ) ) {
add_action( 'send_headers', array( $this, 'add_security_headers' ) );
}
// Restrict REST API.
if ( ARKSP_Plugin::get_setting( 'restrict_rest_api', true ) ) {
add_filter( 'rest_authentication_errors', array( $this, 'restrict_rest_api' ) );
}
// Disable application passwords for non-admins.
if ( ARKSP_Plugin::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 ( ARKSP_Plugin::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 ( ARKSP_Plugin::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 ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- WordPress core constant.
}
}
/**
* 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 = ARKSP_Plugin::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 = ARKSP_Plugin::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.', 'arkhost-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 ( ARKSP_Plugin::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;
}
}