CloudFlare recently added Firewall Rules to all accounts, including free accounts. With these rules, you can help protect your WordPress installation against common attacks for both known and undisclosed vulnerabilities.
Note: Rules on this page are updated from time to time. Last update: April 9, 2021.
First, log in to your CloudFlare account, then click the Firewall button in the toolbar at the top of the page.
Next, click the Firewall Rules tab. Then click the Create a Firewall Rule button.
LOGIN PROTECTION FIREWALL RULE
The first firewall rule we will add will help protect the WordPress login page.
- Give your rule a name, such as “Login Protection.”
- Select URI Path as the Field, contains as the Operator, /wp-login.php as the Value.
Note: The reason we don’t use equals as the Operator is because it could be easily circumvented if the attacker used a URI Path of //wp-login.php (a double slash in a URI Path is interpreted as a single slash on most web servers). Double slashes are not an issue if you enable CloudFlare’s URL Normalization. But we still want to use the contains operator instead of the equals operator to prevent other obfuscation of the URI.
- Set the rule Action to Challenge (Captcha).
Your rule form should look like this:
Click the Deploy button. Test your new firewall rule by trying to log in to your WordPress web site. You should be presented with a CAPTCHA which you must answer correctly before being given access to the WordPress login page. If not, check your firewall rule for typos and selection errors.
This firewall rule will stop all automated attacks on your login page. However, it will not stop login attacks against the REST or XMLRPC interfaces of your WordPress site. There are many security plugins available for WordPress that can help protect those interfaces from attacks on your web site.
VULNERABLE PLUGIN AND ROGUE PHP FILE BLOCKING RULE
The next firewall rule we will add will help protect your WordPress web site from attacks on vulnerable plugins. It will also stop access to PHP files a hacker might have managed to upload to your site.
- Give your rule a name, such as “Content Protection.”
- Select URI Path as the Field, contains as the Operator, and /wp-content/ as the Value.
- Click the And button to add another match specification.
- For the added match specification, select URI Path as the Field, contains as the Operator, and .php as the Value.
- Set the rule Action to Block.
Your rule form should look like this:
Click the Deploy button. Test your new firewall rule by trying to directly access any PHP file in the /wp-content/ folder. For example, if you have the WP Super Cache plugin installed, try accessing https://<my-web-site.com>/wp-content/plugins/wp-super-cache/wp-cache.php and you should get an Access Denied response from CloudFlare.
Note: Properly designed themes and plugins should never require direct access to PHP files in the /wp-content/ folder. If your theme or one of your plugins stops working after adding this firewall rule, be sure to complain to the maker of that theme or plugin. Better yet, uninstall it, because the developer can not be trusted if they made such a basic design mistake.
THE EXPRESSION EDITOR
Under the inputs for the Firewall Rule form you will notice the Expression Preview text box. This provides an easy way to copy and share Firewall Rules. Click the Edit Expression link to switch into the Expression Editor mode.
EXPRESSION EDITOR EXAMPLES
Here are just a few examples of what you can do with the Expression Editor:
- Block attempts to download the WordPress configuration or setup files…
(http.request.uri contains "wp-config.") or (http.request.uri contains "setup-config.")
- Block all attempts to access WordPress REST API…
(http.request.uri.path contains "/wp-json/")
Do not use this rule if you use the WordPress Block Editor, or have JetJack or other plugins installed that rely on REST.
- Block author name scanning (sometimes a precursor to brute-force login attacks, but could also be search engines trying to discover content)…
(http.request.uri.query contains "author_name=") or (http.request.uri.query contains "author=" and not http.request.uri.path contains "/wp-admin/export.php") or (http.request.uri contains "/wp-json/wp/v2/users/") or
Note: Blocking /wp-json/wp/v2/users/ might result in a lost of features in WordPress’ Block Editor.
- Block attempts to access phpMyAdmin through your public web site…
(lower(http.request.uri.path) contains "phpmyadmin")
Hopefully you aren’t allowing that anyway, but why waste your web server’s time processing attempts to access it?
- Block attempts to access the filesystem outside of the web site’s directory…
(raw.http.request.uri contains "../") or (raw.http.request.uri contains "..%2F")
%2F is the URL encoded value of / which is often used in scripting attacks. The raw. prefix allows us to examine the URI contents before CloudFlare’s URL Normalization is performed. URL Normalization prevents attempts to access the filesystem outside the web site’s directory, but it also prevents us from catching and blocking these attacks. Add this rule if you would like to see these attacks and where they are coming from in the CloudFlare’s firewall log.
- Block attempts to access the password file by exploiting buggy plugins or themes…
(http.request.uri contains "passwd")
- Block bad bots and unwanted user agents with a substring search of the user agent string…
(http.user_agent contains "Nimbostratus")
Searching for a substring keeps this rule working if there are minor changes to the user agent string, such as a version number change.
- Block spammers, bots and other bad actors (as calculated by CloudFlare’s AI systems)…
(cf.threat_score gt 15)
(http.request.uri.path contains ".js.map")
COMBINE FIREWALL RULES
As we can see, the expression editor allows the use of parenthesis and the logical operators and and or. This means we can combine all rules that use the same Action into a single rule. To protect a WordPress site with all the rules we have made so far, we can reduce them down to two rules; one that combines all the Block rules, and another for the Challenge action when someone accesses the login page.
- Unwanted Bots and User Agents:
(cf.threat_score gt 15) or (http.user_agent contains "AhrefsBot") or (http.user_agent contains "baidu") or (http.user_agent contains "/bin/bash") or (http.user_agent contains "curl/") or (http.user_agent contains "DnyzBot") or (http.user_agent contains "DotBot") or (http.user_agent contains "eval(") or (http.user_agent contains "Go-http-client") or (http.user_agent contains "Nikto") or (http.user_agent contains "Nimbostratus") or (lower(http.user_agent) contains "python") or (lower(http.user_agent) contains "pycurl") or (http.user_agent contains "Scrapy") or (http.user_agent contains "SeznamBot") or (http.user_agent contains "Sogou") or (http.user_agent contains "spbot") or (http.user_agent contains "Uptimebot") or (http.user_agent contains "WebDAV-MiniRedir") or (http.user_agent contains "WinHttp.WinHttpRequest") or (http.user_agent contains "YandexBot") or (http.user_agent contains "ZmEu")
- Content Protection:
(http.request.uri.query contains "author_name=") or (http.request.uri.query contains "author=" and not http.request.uri.path contains "/wp-admin/export.php") or (http.request.uri contains "/wp-json/wp/v2/users/") or (http.request.uri contains "wp-config.") or (http.request.uri contains "setup-config.") or (http.request.uri.path contains "/wp-content/" and http.request.uri.path contains ".php") or (http.request.uri.path contains ".js.map") or (lower(http.request.uri.path) contains "phpmyadmin") or (lower(http.request.uri.path) contains "thinkphp") or (http.request.uri.path contains "/phpunit") or (raw.http.request.uri contains "../") or (raw.http.request.uri contains "..%2F") or (http.request.uri contains "passwd") or (http.request.uri contains "/dfs/") or (http.request.uri contains "/autodiscover/") or (http.request.uri contains "/wpad.") or (http.request.uri contains "/wallet.dat") or (http.request.uri contains "webconfig.txt") or (http.request.uri contains "vuln.") or (http.request.uri contains ".env") or (http.request.uri.query contains "bin.com/") or (http.request.uri.query contains "bin.net/") or (raw.http.request.uri.query contains "?%00") or (http.request.uri.query contains "base64") or (http.request.uri.query contains "var_dump") or (http.request.uri.query contains "<script") or (raw.http.request.uri.query contains "%3Cscript") or (http.request.full_uri contains "<?php") or (http.cookie contains "<?php") or (http.cookie contains "<script") or (http.referer contains "%3Cscript") or (http.cookie contains "base64") or (http.cookie contains "var_dump") or (upper(http.request.uri.query) contains "$_GLOBALS[") or (upper(http.request.uri.query) contains "$_REQUEST[") or (upper(http.request.uri.query) contains "$_POST[")
- Login Protection:
Action: Challenge (Captcha)
(http.request.uri.path contains "/wp-login.php")
If you see strange behavior with your web site after enabling any firewall rules, here are some steps to try to track down the problem.
- Check the Events tab on the Firewall page. If you see your current IP address listed in the events, then one of your firewall rules is being triggered by your actions on your web site.
- Click on any event with a Service column labeled “Firewall rule” to view details of the event. This will show you which firewall rule triggered the event. Hover your pointer over the Rule ID to reveal a Filter button. Clicking the Filter button will take you directly to the editor for that Firewall Rule. If you still can’t figure out what is triggering a firewall event, try the following steps.
- Disable all your firewall rules (toggle the switch next to each rule) and see if the problems you are observing are fixed. Your IP address should not be showing up in any new firewall events listed in the Events tab.
- Next, re-enable each firewall rule one at a time, testing your web site after re-enabling each one. When the problem on your web site returns, you know you have found the rule causing the problem.
- Next, use the Expression Editor to copy-and-paste the entire rule that is causing the problem to a temporary document. Then delete individual lines from the rule, and save the rule after each line you delete. Check your web site to see if the problem went away. If it did, you found the line causing the problem. If not, continue deleting lines, saving, and testing your web site until you find the line causing the problem.
- Once you have isolated the problem, use the Expression Editor to restore the original rule from your temporary copy. Then delete the one line that was causing the problem. Save the edited rule and verify the problem has gone away. If not, more than one line may be causing a problem, most likely a line after the one you deleted. Repeat the previous procedure until you find the new line causing the problem.
- Rather than just eliminating the line, or lines, that caused the problem, see if you can determine a way to make a more specific filter that will protect your web site without causing problems for normal web server traffic.
- Searching for signs of SQL Injection attacks like UNION ALL and SELECT won’t catch much because it is easy to mix comments into those strings and still have them be valid SQL. A regular expression search would make it easier to detect SQL Injection attacks. CloudFlare provides the matches regex operator. However, even though the documentation does not state that the matches operator is restricted in any way, any attempt to save a firewall rule using a matches expression in a CloudFlare Free or Pro account will result in a message of…
not entitled to use regexp pattern in expression, at index: 0
If you have a paid CloudFlare account that allows for regex operators, you are better off using the included Cloudflare Web Application Firewall service to block SQL Injection attacks instead of rolling your own.
Check out the CloudFlare Firewall Rules documentation for more information and ideas on how to put this powerful feature to work.
These firewall rules will stop a lot of attacks on your web site. Just return to your CloudFlare account in 24 hours and look at the Firewall Events log at the bottom of the Firewall page. You will see at least a dozen attacks blocked by these rules. Don’t take it personally, these attackers most likely do not know you. They most likely just want to add your web server to their army of ’bots for more nefarious purposes.
However, this is not 100% protection for your WordPress site. Some additional things you can do to protect your site are:
- In your CloudFlare account, go to Firewall >> Bots >> Bot Fight Mode, and enable it. Let CloudFlare’s AI do battle with ’bots before they touch your system.
- Keep WordPress, your theme and plugins up-to-date. Read the Configuring Automatic Background Updates article to learn how to update your web site as soon as updates are available.
- Review and follow the advice given in the Hardening WordPress article.
- Add a WordPress security plugin like NinjaFirewall.
- Install and configure server level protection, such as mod_security, Fail2Ban, and bad bot blockers. Note: If you aren’t running your own web server, your web hosting company should be taking care of server level protection for you.
- Read more details on what fields and expressions are possible with CloudFlare’s firewall rules.
- Cloudflare offers a paid Web Application Firewall service which is a like having the firewall rules outlined in this article, plus many more, all kept up-to-date by their engineers.