Skip to content

Instantly share code, notes, and snippets.

@posixpascal
Last active March 3, 2024 23:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save posixpascal/aca919dc76b2bcc3078e870ef9f50853 to your computer and use it in GitHub Desktop.
Save posixpascal/aca919dc76b2bcc3078e870ef9f50853 to your computer and use it in GitHub Desktop.
wordpress hardening snippet
<?php
// 1. Please read this snippet carefully before continuing. [It may break your site if you are not careful]
// 2. Paste this snippet into ./wp-content/mu-plugins/security.php
// 3. Log-in to WP Dashboard once to apply the .htaccess changes
/**
* Copyright 2024 xupx - <pascal@xupx.de> & more
*
* ===================================
* Make Wordpress Safe Again
*
* This snippet is meant as a drop-in "must-use" plugin to make various safe wordpress adjustments
*
* @php >= 7.4
* @wordpress >= 6.0
*/
// -> Configuration
const DISABLE_XMLRPC = true; // Disables WP XML RPC Endpoint which is often targeted for attacks
const DISABLE_PINGBACKS = true; // Disables WP Pingback Feature where a blog notifies another.
const DISABLE_FILE_EDITOR = true; // Disables File editor (malicous actors can gain RW access to all files using this)
const DISABLE_REST_ENDPOINTS = true; // Disables WP-JSON Authors Listing
const DISABLE_PLUGIN_UPDATE_MAIL = true; // Disables E-mail Notification when updating plugins
const DISABLE_THEME_UPDATE_MAIL = true; // Disables E-mail Notficiation when updating themes
const DISABLE_FEEDS = true; // Disable RSS feeds
const DISABLE_DEBUG = true; // Enforces removal of debug mode (except on localhost, see below)
const DISABLE_WP_AUTO_UPDATE = true; // Never automatically try to update wordpress
const REMOVE_WP_VERSION_FROM_SNIPPETS = true; // Remove ?ver= hint from scripts and snippets
const REMOVE_HEAD_DATA = true; // Remove unnecessary <head> meta tags
const REMOVE_MULTISITE_SIGNUP = true; // Removes redirect from multisite to signup.php
const REMOVE_WP_EMBED = true; // Removes oembed.js from loading in footer
const FORCE_SSL_ADMIN = true; // Forces SSL connection in admin menu
// -> .htaccess (this may break your site, if it does, edit this file and remove
// all of lines between the ### <Section> START ### & ### <Section> END ### block
// then disable some htaccess rules here and re-login into the backend.
const HTA_DISABLE_SERVER_SIGNATURE = true; // Removes X-Powered-By and other http header hints
const HTA_DISABLE_PHP_ERRORS = true; // Disables php error log
const HTA_DISABLE_INDEX = true; // Disables directory listing
const HTA_DISABLE_WP_FOLDERS = true; // Disables access to critical files (see below)
const HTA_DISABLE_AUTHORS = true; // Disables authors rest endpoint via htaccess
const HTA_DISABLE_XMLRPC = true; // Removes xmlrpc access via htaccess
const HTA_SECURE_PHP_SESSION = false; // Uses secure php session cookies
const HTA_DISABLE_EXPOSE_PHP = false; // Removes php powered-by header
// -> Helper Methods
function hta_edit($section, $rules){
$sectionHead = "### $section START ###";
$sectionFoot = "### $section END ###";
$src = file_get_contents(ABSPATH . ".htaccess");
$pattern = "/(" . preg_quote($sectionHead, '/') . ")(.*?)" . preg_quote($sectionFoot, '/') . "/s";
// Check if the section already exists
if (preg_match($pattern, $src)) {
// Update the existing section with new rules
$newSrc = preg_replace_callback($pattern, function($matches) use ($sectionHead, $rules, $sectionFoot) {
// Construct replacement string manually, escaping $ as needed (some htaccess snippets use $1)
return $sectionHead . "\n" . str_replace('$', '\$', $rules) . "\n" . $sectionFoot;
}, $src);
} else {
// Append new section at the end if it doesn't exist
$newSrc = $src . "\n" . $sectionHead . "\n" . $rules . "\n" . $sectionFoot . "\n";
}
file_put_contents(".htaccess", $newSrc);
}
function nope()
{
return false;
}
function nothing()
{
return "";
}
function no($script)
{
return function () use ($script) {
wp_deregister_script($script);
};
}
function disable_pingbacks($headers)
{
unset($headers['X-Pingback']);
return $headers;
}
function remove_wp_version($src)
{
return remove_query_arg("ver", $src);
}
function remove_multisite_signup()
{
if (!is_user_logged_in()) {
wp_redirect(site_url());
exit;
}
}
// -> Perform security measures
if (DISABLE_XMLRPC)
add_filter('wp_xmlrpc_server_class', 'nope') && add_filter('xmlrpc_enabled', 'nope');
if (DISABLE_PINGBACKS)
add_filter('wp_headers', 'disable_pingbacks') && add_filter('pings_open', 'nope');
if (DISABLE_REST_ENDPOINTS) {
add_filter('rest_endpoints', function ($endpoints) {
if (is_user_logged_in()) {
return $endpoints;
}
foreach ($endpoints as $route => $endpoint) {
if (strpos($route, '/wp/v2/') === 0) {
unset($endpoints[$route]);
}
}
return $endpoints;
});
}
if (DISABLE_PLUGIN_UPDATE_MAIL) {
add_filter('auto_plugin_update_send_email', 'nope');
}
if (DISABLE_THEME_UPDATE_MAIL) {
add_filter('auto_theme_update_send_email', 'nope');
}
if (DISABLE_FEEDS) {
/**
* List of feeds to disable
*/
$feeds = [
'do_feed',
'do_feed_rdf',
'do_feed_rss',
'do_feed_rss2',
'do_feed_atom',
'do_feed_rss2_comments',
'do_feed_atom_comments',
];
foreach ($feeds as $feed) {
add_action($feed, function () {
wp_die('Feed has been disabled.', '', ['response' => 403]);
}, 1);
}
}
if (REMOVE_HEAD_DATA) {
// Remove post and comment feed link
remove_action('wp_head', 'feed_links', 2);
// Remove post category links
remove_action('wp_head', 'feed_links_extra', 3);
// Remove link to the Really Simple Discovery service endpoint
remove_action('wp_head', 'rsd_link');
// Remove the link to the Windows Live Writer manifest file
remove_action('wp_head', 'wlwmanifest_link');
// Remove the XHTML generator that is generated on the wp_head hook, WP version
remove_action('wp_head', 'wp_generator');
// Remove start link
remove_action('wp_head', 'start_post_rel_link');
// Remove index link
remove_action('wp_head', 'index_rel_link');
// Remove previous link
remove_action('wp_head', 'parent_post_rel_link', 10, 0);
// Remove relational links for the posts adjacent to the current post
remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0);
// Remove relational links for the posts adjacent to the current post
remove_action('wp_head', 'wp_oembed_add_discovery_links');
// Remove REST API links
remove_action('wp_head', 'rest_output_link_wp_head');
// Remove Link header for REST API
remove_action('template_redirect', 'rest_output_link_header', 11, 0);
// Remove Link header for shortlink
remove_action('template_redirect', 'wp_shortlink_header', 11, 0);
}
if (REMOVE_MULTISITE_SIGNUP) {
add_action('signup_header', 'remove_multisite_signup');
}
if (REMOVE_WP_VERSION_FROM_SNIPPETS) {
add_filter('the_generator', 'nothing');
add_filter('style_loader_src', 'remove_wp_version', PHP_INT_MAX);
add_filter('script_loader_src', 'remove_wp_version', PHP_INT_MAX);
}
if (REMOVE_WP_EMBED) {
add_action('wp_footer', no('wp-embed'));
}
if (DISABLE_FILE_EDITOR) {
define('DISALLOW_FILE_EDIT', true);
}
if (DISABLE_DEBUG && !in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1', 'localhost'])) {
define('WP_DEBUG', false);
define('WP_DEBUG_DISPLAY', false);
}
if (DISABLE_WP_AUTO_UPDATE) {
define('WP_AUTO_UPDATE_CORE', false);
}
if (FORCE_SSL_ADMIN){
define('FORCE_SSL_ADMIN', true);
}
function hta_changes(){
if (HTA_DISABLE_SERVER_SIGNATURE){
hta_edit("Disable Server Signature", <<<EOT
ServerSignature Off
Header unset SERVER
Header unset X-Pingback
Header unset X-Powered-By
EOT);
}
if (HTA_DISABLE_PHP_ERRORS){
hta_edit("PHP Errors Off", <<<EOT
php_flag display_errors off
EOT);
}
if (HTA_DISABLE_INDEX){
hta_edit("Disable Directory Listing", <<<EOT
Options All -Indexes
EOT);
}
if (HTA_DISABLE_WP_FOLDERS){
hta_edit("Disable File Access", <<<EOT
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^(wp-content/uploads/.+.php)$ $1 [H=text/plain]
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteCond %{SCRIPT_FILENAME} !^(.*)wp-includes/ms-files.php
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
<Files xmlrpc.php>
Order Allow,Deny
Deny from all
</Files>
<Files install.php>
Order allow,deny
Deny from all
Satisfy All
</Files>
<FilesMatch "(wp-config.php|wp-config-sample.php|liesmich.html|readme.html)">
Order allow,deny
Deny from all
Satisfy All
</FilesMatch>
<Files ~ "^[\._]ht">
Order Allow,Deny
Deny from all
Satisfy All
</Files>
<FilesMatch "(\.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist|txt)|~)$">
Order allow,deny
Deny from all
Satisfy All
</FilesMatch>
<Files robots.txt>
Order allow,deny
Allow from all
</Files>
EOT);
}
if (HTA_DISABLE_AUTHORS){
hta_edit("Disable Authors Endpoint", <<<EOT
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} ^/$
RewriteCond %{QUERY_STRING} ^/?author=([0-9]*) [NC]
RewriteRule ^(.*)$ http://%{HTTP_HOST}/? [L,R=301,NC]
</IfModule>
EOT);
}
if (HTA_DISABLE_XMLRPC){
hta_edit("Disable XMLRPC", <<<EOT
<Files "xmlrpc.php">
Order Deny,Allow
Deny from All
</Files>
EOT);
}
if (HTA_SECURE_PHP_SESSION){
hta_edit("Secure PHP Session", <<<EOT
php_value session.cookie_httponly On
php_value session.cookie_secure On
php_flag session.use_only_cookies On
php_flag session.use_strict_mode On
EOT);
}
if (HTA_DISABLE_EXPOSE_PHP){
hta_edit("Stop PHP Expose", <<<EOT
php_value expose_php Off
EOT);
}
}
add_action('wp_login', 'hta_changes', 10, 2);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment