RE:NODE
ჰოსტინგი

ვებ ჰოსტინგი12 წუთის საკითხავი

WordPress security hardening that is worth the effort

Update policy, logins, file permissions, xmlrpc, plugin hygiene and backups - the WordPress hardening that stops real attacks, and the theatre to skip.

ეს სტატია ჯერ ინგლისურადაა. ვთარგმნით.

0 მკითხველი

WordPress core is not usually how a site is broken into. The way in is an out-of-date plugin with a public advisory, a password reused from somewhere that leaked, or a file the web server should never have been allowed to write. Hardening that ignores those three and instead hides the version number in the page source is decoration.

So this is the honest order: keep things updated, make the login worth nothing to an attacker, stop PHP running from places it has no business running, run fewer plugins from people who are still maintaining them, and hold backups somewhere the compromise cannot reach. Everything below is one of those five, and where a popular tip does nothing, it says so.

How WordPress sites are actually broken into#

Four routes account for nearly all of it.

  • A vulnerable plugin or theme. Someone publishes an advisory, automated scanners start probing every site on the internet for that plugin's path within hours, and sites that have not updated in a week are found. This is the single largest category and it is entirely a patching problem.
  • Credentials. Brute force against wp-login.php, but more often credential stuffing with a password that leaked from an unrelated service. Also stolen SFTP or panel credentials, which usually means the compromised machine was the owner's laptop, not the server.
  • Abandoned or sold plugins. A plugin with 40,000 installs stops being maintained, or changes hands, and the new owner ships an update that loads remote code. Rare, and unpleasant when it happens, because the update itself is the attack.
  • The neighbours. On shared hosting with sloppy isolation, a compromise of one site reaches the next through world-writable files or a shared database user. Container-per-site hosting removes this one, which is a real difference between hosting models rather than a marketing line.

Notice what is missing: a zero-day in WordPress itself. Core is audited heavily, ships security releases quickly, and installs them by itself. If your site is compromised, look at the plugin list and the access logs before you look at core.

Updates, and which ones to automate#

Since 3.7 WordPress installs its own minor and security releases. Leave that alone. The constant that controls it belongs in wp-config.php and has three meaningful values:

wp-config.php
define( 'WP_AUTO_UPDATE_CORE', 'minor' );

'minor' is the default and the right answer for almost everyone: 6.7.1 to 6.7.2 happens on its own, 6.7 to 6.8 waits for you. true takes major versions automatically as well, which is fine on a simple site and risky on one with a page builder. false turns core updates off entirely and is only defensible if something else applies them within a day.

Plugin and theme auto-updates are per item, toggled from the Plugins screen since WordPress 5.5. A workable policy:

ItemAuto-updateReason
Core minor and securityYesNever broken anything meaningful in years
Security and utility pluginsYesThe advisory window is measured in hours
Page builder, theme, WooCommerceNoUpdate deliberately, after a backup
Anything custom or patchedNoAn update overwrites your changes

Whatever you do not automate, you must actually do. An update queue nobody empties is worse than auto-updates, because it produces the illusion of control. Put a recurring task in the calendar, check it against the advisories for the plugins you run, and take a backup immediately before each batch so that "the update broke the site" is a five-minute problem.

The login page and the accounts behind it#

WordPress ships no two-factor authentication in core, so add it with a plugin and require it for everyone with an administrator or editor role. That single change makes password leaks mostly irrelevant, which is the largest return available for ten minutes of work.

Then reduce the value of the login page itself:

  1. No account called `admin`, `administrator`, or the site name. Those three are what automated attempts use. Create a new administrator, log in as it, delete the old one and reassign its posts.
  2. Unique passwords, generated, stored in a manager. The password that also opens a forum account from 2016 is not a password.
  3. Rate limit login attempts. Any of the login-limiting plugins does this: a handful of failures from one address, then a lockout. It converts credential stuffing from thousands of attempts a minute into something that takes years.
  4. Separate roles from people. Contributors and editors do not need administrator. An account that can install plugins is an account that can install a backdoor.
  5. Review the user list monthly. Remove people who left. A dormant administrator account belonging to a developer you worked with in 2023 is a liability with nobody watching it.

Application passwords, added in WordPress 5.6, are the right way to let an external tool talk to the REST API: one password per integration, revocable individually, useless for logging in to the admin. If a plugin asks you for your actual account password to connect a service, that is a reason to distrust the plugin.

Changing the login URL with a plugin is obscurity rather than security, and it breaks flows that expect wp-login.php. It does reduce the volume of junk in your logs, which has some value on a small plan where those requests cost CPU. Judge it on that, not on safety. Blocking /wp-admin and /wp-login.php to everything except a known address list is the real version of this idea, and it is excellent when your team has fixed addresses.

XML-RPC, the REST API and user enumeration#

xmlrpc.php is the old remote interface. It is still used by the mobile app, Jetpack and a few publishing tools, and if nothing in your stack uses it you should close it. It attracts two kinds of traffic: password guessing, and pingback abuse where your site is made to send requests to a third party.

nginx
location = /xmlrpc.php {    deny all;    access_log off;}

On Apache the equivalent goes in .htaccess at the site root:

.htaccess
<Files "xmlrpc.php">    Require all denied</Files>

The REST API is different and must not be blanket-blocked: the block editor uses it, and so do plugins you care about. What is worth restricting is user enumeration. By default, /wp-json/wp/v2/users lists accounts with published posts, /?author=1 redirects to the author's slug, and wp-sitemap.xml includes an author sitemap. None of that is a vulnerability - a username is not a secret - but it hands an attacker the first half of a credential pair for free. If you have no author archives to lose, a hardening plugin can turn off all three, and disabling the author sitemap is a one-line filter.

The other endpoint to think about is wp-cron.php. WordPress runs scheduled jobs on visitor requests, so every hit can trigger cron work, and on a busy site attackers will happily request that file in a loop to make PHP do the expensive thing repeatedly. Set define( 'DISABLE_WP_CRON', true ); and call it on a real schedule instead - a panel's scheduled task hitting the URL once a minute is both faster and cheaper than the default.

File permissions and the two constants that lock the admin#

Directories at 755, files at 644, wp-config.php at 640. Never 777, which means any account on the machine may rewrite your code. If something will not write at 755, the problem is ownership rather than the mode, and chmod 777 is the way to convert a permissions problem into a compromise.

Two constants remove most of the damage an attacker can do with an administrator session:

wp-config.php
define( 'DISALLOW_FILE_EDIT', true );define( 'DISALLOW_FILE_MODS', true );

The first removes the theme and plugin editors from the admin - the feature that turns "stolen password" into "arbitrary PHP on your server" with three clicks. Everyone should set it. The second goes further and blocks plugin and theme installation and updates entirely from the dashboard, which is correct for a site deployed from a repository and wrong for a site whose owner updates plugins by hand.

The other rule is that nothing in the uploads directory may execute. A media upload that slips a .php file past a badly written plugin is worthless if the server refuses to run it:

nginx
location ~* ^/wp-content/uploads/.*\.(?:php|phtml|php5)$ {    deny all;}

Finally, check what is sitting in the web root that should not be. Old wp-config.php.bak files, a site-backup.zip from a migration, an exported database.sql, a .git directory: all of those are downloadable by anyone who guesses the name, and scanners guess constantly. Firewall rules that matter covers the same principle one layer down.

Plugin and theme hygiene#

Every plugin is code you did not write running with full access to your database. The number to care about is not how many you have but how many you cannot account for.

  • Delete, do not deactivate. A deactivated plugin still has files on disk, and several historical vulnerabilities were exploitable without the plugin being active. The same goes for the three default themes you do not use - keep one, delete the rest.
  • Check maintenance before installing. Last updated date, tested-up-to version, open support threads, number of installs. A plugin last updated three years ago is a decision to maintain it yourself.
  • Never install a nulled theme or plugin. Paid code circulating for free is modified, and the modification is the business model. This is the most reliable way to be compromised on day one.
  • Prefer one plugin over three. Three plugins doing a third of a job each is three update streams and three attack surfaces.
  • Subscribe to advisories for the plugins you run. The vulnerability databases publish feeds; a scanner plugin will also tell you, though it tells you after the fact.

Secrets, the database user and the hosting account#

The database user WordPress connects as should have full rights on its own database and no rights anywhere else. Not the administrative account, not a user shared with a second site. If a plugin is exploited through SQL injection, that grant is the boundary between one site lost and every site on the account lost - the reasoning is worked through in the database security checklist.

Rotate the salts in wp-config.php after any suspected compromise. Replacing those eight constants invalidates every session cookie in existence, including an attacker's. Fresh ones come from https://api.wordpress.org/secret-key/1.1/salt/.

Then secure the account above WordPress, which is the one people forget. On RE:NODE that means TOTP two-factor on the panel account with the recovery codes stored somewhere you will still have them, subusers with narrow roles instead of sharing one login - console only, files only, no billing - and API keys restricted by address. Each server has its own SFTP credentials rather than one account key that opens everything, and there is a per-server activity log showing who did what. Two-factor on your panel account and subusers and least privilege cover both properly. An account with both the authenticator and the recovery codes lost cannot be recovered, so store them apart.

Backups that survive the compromise#

A backup on the same disk as the site is not a backup, and a backup you have never restored is a hypothesis. For WordPress, a complete one is two halves - the files under wp-content and the database - and the restore procedure has to cover both or you get a site whose posts reference media that no longer exists.

  • Take one before every update batch. This is the backup you will actually use, and probably this month.
  • Keep copies off the machine. Ransomware and a fat-fingered delete both take everything in reach.
  • Test a restore into a staging copy twice a year. Testing a restore before you need it is the whole argument.
  • Know your retention. A compromise discovered three weeks late needs a backup older than three weeks, and a seven-day rotation cannot provide one. Keep a monthly copy outside the rotation.

RE:NODE web plans include backup slots created on demand or on a schedule from the Schedules tab, stored off the machine they protect, restorable with a button, and lockable against rotation - which is exactly what that monthly copy needs. One limit worth knowing in advance: deleting a server deletes its backups, locked ones included, so anything that must outlive the account gets downloaded.

What a security plugin does, and what it cannot#

A scanner and firewall plugin - Wordfence, Sucuri, Solid Security and the like - is worth running, with a clear view of what you are getting. It can compare core files against the official checksums and spot a modified one, alert on new administrator accounts, limit login attempts, and apply virtual patches for known plugin vulnerabilities before you have updated.

It cannot protect against anything that runs before PHP does, and it is itself PHP: a request that reaches the firewall plugin has already booted WordPress, so under a flood it adds work rather than removing it. It cannot see a compromise that arrived through your SFTP credentials and edited a file with a valid session. And its malware scanner produces false positives on minified JavaScript with cheerful regularity.

Run it, act on what it finds, and do not let it replace the update discipline. If the worst has already happened, containment order matters more than tooling - what to do when your server is hacked has the sequence: isolate, rotate every credential, restore from a backup older than the intrusion, then find the door before you put it back online.

FAQ#

Does changing the wp_ table prefix improve security?

No. Anything that can query your database can enumerate its tables, so the prefix is not hidden from an attacker who is already inside. It is useful for hosting two sites in one database and nothing else, and changing it on a live site risks breaking the admin for no gain.

Should I hide the WordPress version number?

It costs nothing and buys almost nothing. Scanners fingerprint sites by probing for files rather than by reading the generator tag, and they attack whether or not the version matches. Spend the effort on updating instead.

Is it safe to disable XML-RPC?

Yes, if nothing you use needs it. Jetpack, the WordPress mobile app and some remote publishing tools do. Disable it, then check that the app and your plugins still work; if something breaks, allow the file and rate limit it rather than leaving it wide open.

How many plugins is too many?

There is no number. Twenty well-maintained plugins are safer than five abandoned ones. The real limit is how many you can keep patched and justify - if you cannot say what a plugin does and when it last shipped an update, it is one too many.

My host says the site is sending spam. What now?

Almost always a compromise that installed a mailer script. Take the site offline, change every password including SFTP and the panel, look for recently modified PHP files under wp-content/uploads and wp-content/plugins, and restore from a backup predating the change rather than deleting files one at a time.

Do I still need backups if my host takes them?

Yes. Platform backups protect the platform, and on any host, deleting your own server can delete its backups with it. Keep at least one copy of the files and database somewhere you control, and know how long your own restore takes.


კომენტარები

სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.

0/2000