{"id":2538,"date":"2020-04-17T22:08:05","date_gmt":"2020-04-18T02:08:05","guid":{"rendered":"https:\/\/blog.jonesling.us\/?p=2538"},"modified":"2020-04-28T17:18:55","modified_gmt":"2020-04-28T21:18:55","slug":"securing-wordpress-the-basics","status":"publish","type":"post","link":"https:\/\/blog.jonesling.us\/?p=2538","title":{"rendered":"Securing WordPress: The Basics"},"content":{"rendered":"<p>This is the first in an occasional series of documents on <a href=\"https:\/\/blog.jonesling.us\/?tag=wordpress\">WordPress<\/a>.<\/p>\n<hr \/>\n<p><a href=\"https:\/\/wordpress.org\/\">WordPress<\/a> is ubiquitous but fragile.\u00a0 There are few alternatives that provide the easy posting, wealth of plugins, and integration of themes, while also being (basically) free to use.<\/p>\n<p>It&#8217;s also a nerve-wracking exercise in keeping bots and bad actors out.\u00a0 Some of the historical security holes are legendary.\u00a0 It doesn&#8217;t take long to find someone who experienced a site where the comments section was bombed by a spammer, or even outright defacement.\u00a0 (I will reluctantly raise my own hand, having experienced both in years past.)<\/p>\n<p>Most people that use WordPress nowadays rely on 3rd parties to host it.\u00a0 This document isn&#8217;t for them; hosted security is mostly outside of your control.\u00a0 That&#8217;s generally a good thing: professionals are keeping you up to date and covered by best practices.<\/p>\n<p>The rest of us muddle through security and updates in piece-meal fashion, occasionally stumbling over documents like this one.<\/p>\n<h1>Things To Look Out For<\/h1>\n<p>As a rule, good server hygiene demands that you keep an eye on your logs.\u00a0 Tools like <a href=\"https:\/\/goaccess.io\/\">goaccess<\/a> help you analyze usage, but nothing beats a peek at the raw logs for noticing issues cropping up.<\/p>\n<h2>The Good Bots<\/h2>\n<p>Sleepy websites like mine show a high proportion of &#8220;good&#8221; bots like Googlebot, compared to human traffic.\u00a0 They&#8217;re doing good things like crawling (indexing) your site.<\/p>\n<p>In my case they are the primary visitor base to my site, generating hundreds or even thousands of individual requests per day.\u00a0 Hopefully your own WordPress site has a better visitor-to-bot ratio than mine.<\/p>\n<p>We don&#8217;t want to block these guys from their work, they&#8217;re actually helpful.<\/p>\n<h2>The Bad Bots<\/h2>\n<p>You&#8217;ll also see bad bots, possibly lots of them.\u00a0 Most are attempting to guess user credentials so they can post things on your WordPress site.<\/p>\n<p>Some are fairly up-front about it:<\/p>\n<blockquote>\n<pre>...\n132.232.47.138 [07:51:14] \"POST \/xmlrpc.php HTTP\/1.1\"\n132.232.47.138 [07:51:14] \"POST \/xmlrpc.php HTTP\/1.1\"\n132.232.47.138 [07:51:15] \"POST \/xmlrpc.php HTTP\/1.1\"\n132.232.47.138 [07:51:16] \"POST \/xmlrpc.php HTTP\/1.1\"\n132.232.47.138 [07:51:16] \"POST \/xmlrpc.php HTTP\/1.1\"\n132.232.47.138 [07:51:18] \"POST \/xmlrpc.php HTTP\/1.1\"\n...<\/pre>\n<\/blockquote>\n<p>They&#8217;ll hammer your server like that for hours.<\/p>\n<p>Blocking their individual IP addresses at the firewall is devastatingly effective&#8230; for about five minutes.\u00a0 Another bot from another IP will pop up soon.\u00a0 Blocking individual IPs is a game of whack-a-mole.<\/p>\n<p>Some are part of a &#8220;slow&#8221; botnet, hitting the same page from unique a IP address each time.\u00a0 These are part of the large botnets you read about.<\/p>\n<blockquote>\n<pre>83.149.124.238 [05:01:06] \"GET \/wp-login.php HTTP\/1.1\" 200\n83.149.124.238 [05:01:06] \"POST \/wp-login.php HTTP\/1.1\" 200\n188.163.45.140 [05:03:38] \"GET \/wp-login.php HTTP\/1.1\" 200\n188.163.45.140 [05:03:39] \"POST \/wp-login.php HTTP\/1.1\" 200\n90.150.96.222 [05:04:30] \"GET \/wp-login.php HTTP\/1.1\" 200\n90.150.96.222 [05:04:32] \"POST \/wp-login.php HTTP\/1.1\" 200\n178.89.251.56 [05:04:42] \"GET \/wp-login.php HTTP\/1.1\" 200\n178.89.251.56 [05:04:43] \"POST \/wp-login.php HTTP\/1.1\" 200<\/pre>\n<\/blockquote>\n<p>These are more insidious: patient and hard to spot on a heavily-trafficked blog.<\/p>\n<h1>Keeping WordPress Secure<\/h1>\n<p>You (hopefully) installed WordPress to a location outside of your &#8220;htdocs&#8221; document tree.\u00a0 If not, you should fix that right away!\u00a0 (Consider this &#8220;security tip #0&#8221; because without this you&#8217;re basically screwed.)<\/p>\n<p>Security tip #1 is to make sure auto updates are enabled.\u00a0 The slight risk of a botched release being automatically applied is much lower than that of having an critical security patch that is applied too late.<\/p>\n<p>Like medieval door locks on your front door, there is little security advantage to running old software.<\/p>\n<p>Once an exploit is patched, the prior releases are vulnerable as people deconstruct the patch and reverse-engineer the exploit(s) &#8211; assuming a exploit wasn&#8217;t published before the patch was released.<\/p>\n<h2>Locking WordPress Down<\/h2>\n<p>Your Apache configuration probably contains a section similar to this:<\/p>\n<pre>&lt;Directory \"\/path\/to\/wordpress\"&gt;\n    ...\n    Require all granted\n    ...\n&lt;\/Directory&gt;<\/pre>\n<p>We&#8217;re going to add some items between &lt;Directory&gt;&lt;\/Directory&gt; tags to restrict access to the most vulnerable pieces.<\/p>\n<h3>You Can&#8217;t Attack Things You Can&#8217;t Reach<\/h3>\n<p>We&#8217;ll start by invoking the <a href=\"https:\/\/www.us-cert.gov\/bsi\/articles\/knowledge\/principles\/least-privilege\">Principle of Least Privilege<\/a>: people should only be able to do the things they must do, and nothing more.<\/p>\n<p>xmlrpc.php is an API for applications to talk to WordPress.\u00a0 Unfortunately it doesn&#8217;t carry extra security, so if you&#8217;re a bot it&#8217;s great to hammer with your password guesses &#8211; you won&#8217;t be blocked, and no one will be alerted.<\/p>\n<p>Most people don&#8217;t need it.\u00a0 Unless you know you need it, you should disable it completely.<\/p>\n<pre>&lt;Directory \"\/path\/to\/wordpress\"&gt;\n    ...\n    &lt;Files xmlrpc.php&gt;\n        &lt;RequireAll&gt;\n            Require all denied\n        &lt;\/RequireAll&gt;\n    &lt;\/Files&gt;\n&lt;\/Directory&gt;<\/pre>\n<p>There are WordPress plugins that purport to &#8220;disable&#8221; xmlrpc.php, but they deny access from within WordPress.\u00a0 That means that you&#8217;ve still paid a computational price for executing xmlrpc.php, which can be steeper than you expect, and you&#8217;re still at risk of exploitable bugs within it.\u00a0 Denying access to it at the server level is much safer.<\/p>\n<h3>You Can&#8217;t Log In If You Can&#8217;t Reach the Login Page<\/h3>\n<p>This next change will block anyone from outside your LAN from logging in.\u00a0 That means that if you&#8217;re away from home you won&#8217;t be able to log in, either, without tunneling back home.<\/p>\n<pre>&lt;Directory \"\/path\/to\/wordpress\"&gt;\n    ...\n    &lt;Files wp-login.php&gt;\n        &lt;RequireAll&gt;\n            Require all granted\n            # remember that X-Forwarded-For may contain multiple\n            # addresses, don't just search for ^192...\n            Require expr %{HTTP:X-Forwarded-For} =~ \/\\b192\\.168\\.1\\.\/\n        &lt;\/RequireAll&gt;\n    &lt;\/Files&gt;\n&lt;\/Directory&gt;<\/pre>\n<p>If you&#8217;re not using a public-facing proxy, and don&#8217;t need to look at X-Forwarded-For, you can simplify this a little:<\/p>\n<pre>&lt;Directory \"\/path\/to\/wordpress\"&gt;\n    ...\n    &lt;Files wp-login.php&gt;\n        &lt;RequireAll&gt;\n            Require all granted\n            Require ip 192.168.1\n        &lt;\/RequireAll&gt;\n    &lt;\/Files&gt;\n&lt;\/Directory&gt;<\/pre>\n<p>This will prevent 3rd parties from signing up on your blog and submitting comments.\u00a0 This may be important to you.<\/p>\n<h2>Restart Apache<\/h2>\n<p>After inserting these blocks, you should execute Apache&#8217;s &#8216;configtest&#8217; followed by reload:<\/p>\n<pre>$ sudo apache2ctl configtest\napache2      | * Checking apache2 configuration ...\u00a0\u00a0\u00a0\u00a0 [ ok ]\n<\/pre>\n<pre>$ sudo apache2ctl reload\napache2      | * Gracefully restarting apache2 ...      [ ok ]<\/pre>\n<p>Now test your changes from outside your network:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2540 size-full\" style=\"border: 1px solid #ccc;\" src=\"https:\/\/blog.jonesling.us\/wp-content\/uploads\/2020\/04\/20200417-Forbidden.png\" alt=\"xmlrpc.php forbidden\" width=\"600\" height=\"300\" \/><\/p>\n<p>Apache&#8217;s access log should show a &#8216;403&#8217; (Forbidden) status:<\/p>\n<pre>... \"GET \/xmlrpc.php HTTP\/1.1\" <strong>403 ...<\/strong><\/pre>\n<p>And just like that, you&#8217;ve made your WordPress blog a <strong>lot<\/strong> more secure.<\/p>\n<p>Interestingly, by making just these changes on my own site the attacks immediately dropped off by 90%.\u00a0 I guess that the better-written bots realized that I&#8217;m not a good target anymore and stopped wasting their time, preferring lower-hanging fruit.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is the first in an occasional series of documents on WordPress. WordPress is ubiquitous but fragile.\u00a0 There are few alternatives that provide the easy posting, wealth of plugins, and integration of themes, while also being (basically) free to use. It&#8217;s also a nerve-wracking exercise in keeping bots and bad actors out.\u00a0 Some of the &hellip; <a href=\"https:\/\/blog.jonesling.us\/?p=2538\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Securing WordPress: The Basics&#8221;<\/span><\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"wprm-recipe-roundup-name":"","wprm-recipe-roundup-description":"","advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[9],"tags":[355,151,555,83,556,354],"class_list":["post-2538","post","type-post","status-publish","format-standard","hentry","category-linux","tag-apache","tag-dad-needs-to-stop-bringing-work-home","tag-how-to","tag-linux","tag-netsec","tag-wordpress"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p4o3FW-EW","jetpack-related-posts":[],"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/posts\/2538","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2538"}],"version-history":[{"count":11,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/posts\/2538\/revisions"}],"predecessor-version":[{"id":2558,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=\/wp\/v2\/posts\/2538\/revisions\/2558"}],"wp:attachment":[{"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2538"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2538"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jonesling.us\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2538"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}